Compare commits

..

40 Commits

Author SHA1 Message Date
Oleksii Holub 5632d6c386 HTML-encode markdown content when formatting is disabled (#1545) 2026-06-04 13:09:03 +03:00
Oleksii Holub f4d1e630f7 Implement missing HTML encoding for several attribute injections (#1544) 2026-06-04 12:38:09 +03:00
Colton 799dd1ad81 Stream asset downloads to avoid OutOfMemoryException on large files (#1540)
* Stream asset downloads to avoid OutOfMemoryException on large files

ExportAssetDownloader used HttpClient.GetAsync(url, ct), which defaults to
HttpCompletionOption.ResponseContentRead and buffers the entire response body
into memory before returning. Downloading a large attachment therefore tried to
grow a single in-memory buffer to the full file size and threw
OutOfMemoryException (HttpContent.LoadIntoBufferAsync -> GrowAndWrite).

Pass HttpCompletionOption.ResponseHeadersRead so the body is streamed straight
to disk via CopyToAsync in bounded chunks, matching how DiscordClient already
issues its requests.

* Update ExportAssetDownloader.cs

---------

Co-authored-by: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com>
2026-06-03 15:47:43 +03:00
dependabot[bot] 64a4ba296e Bump the actions group with 4 updates (#1541)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 19:41:24 +03:00
dependabot[bot] f0e99765f7 Bump the nuget group with 13 updates (#1542)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-02 19:40:41 +03:00
tyrrrz 45629c9d7f Refactor message boxes 2026-05-26 15:00:42 +03:00
Copilot 2aec84884b Extract app shutdown fallback into App.Shutdown (#1539)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
2026-05-25 19:10:19 +03:00
tyrrrz 6258394fc0 Remove lang version from build props 2026-05-03 16:01:32 +03:00
tyrrrz e6472543e0 Refactor fetching Avalonia TopLevel 2026-05-02 11:30:14 +03:00
dependabot[bot] ee163d6de7 Bump actions/upload-artifact from 7.0.0 to 7.0.1 in the actions group (#1528)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 16:31:01 +03:00
tyrrrz 5838c0546e Update for Avalonia 12 2026-05-01 16:30:11 +03:00
dependabot[bot] 07a02773be Bump the nuget group with 15 updates (#1529)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-01 16:05:31 +03:00
tyrrrz 0a623b24d1 Refactor GUI spacing 2026-04-22 17:18:28 +03:00
tyrrrz da4f5ea74a Use PowerKit's Uri.TryGetFilaName() 2026-04-20 22:22:11 +03:00
tyrrrz 34b5ccc75b Remove BOM 2026-04-19 23:13:12 +03:00
Copilot 7456f0fe3a Add PowerKit and replace custom utility extensions (#1525)
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-19 23:10:45 +03:00
tyrrrz 757daa7a60 Forward slashes 2026-04-19 18:13:26 +03:00
tyrrrz 9590d4461a Standardize repo structure 2026-04-19 16:24:23 +03:00
tyrrrz a27fc3e72e Update Avalonia 2026-04-17 15:02:28 +03:00
Oleksii Holub f10bba63e6 Add warning against user account automation 2026-04-14 22:52:10 +03:00
tyrrrz b76b42549b Fix security issues 2026-04-14 13:23:38 +03:00
tyrrrz ae70fe9741 Move theme initialization to the correct place 2026-04-07 17:54:05 +03:00
tyrrrz af3ec8c299 Use CliFx v3 stable 2026-04-03 17:27:30 +03:00
tyrrrz f1673cde77 Use the Loaded event in view init 2026-04-03 11:37:44 +03:00
tyrrrz ed33d19bc0 Fix settings getting corrupted 2026-04-03 11:20:54 +03:00
tyrrrz 80e9ad75ba Clean up 2026-04-03 00:35:09 +03:00
tyrrrz 295f4cf9a2 Improve WatchProperty 2026-04-03 00:24:27 +03:00
tyrrrz 1f189e5206 Rename view model manager methods 2026-04-02 14:50:11 +03:00
tyrrrz df683b3b41 Remove unneeded usings 2026-04-02 14:36:17 +03:00
tyrrrz d467948d0b Only execute commands from code if allowed 2026-04-02 14:36:06 +03:00
tyrrrz f6166764e9 Automate view initialization 2026-04-02 14:35:17 +03:00
dependabot[bot] 7ee2763d4b Bump the nuget group with 17 updates (#1520)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 15:03:58 +03:00
dependabot[bot] eb99ab29ed Bump the actions group with 5 updates (#1519)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-01 15:02:33 +03:00
tyrrrz 5c61306231 Update favicon 2026-03-31 23:47:26 +03:00
tyrrrz 2e4601d378 Emit source-generated files for easier debugging 2026-03-30 21:36:11 +03:00
Oleksii Holub cc703cc860 Migrate to CliFx v3 (#1516) 2026-03-26 23:53:28 +02:00
Oleksii Holub 661f82c478 Add Nix download for GUI 2026-03-24 00:51:42 +02:00
tyrrrz b9f19f7003 Fix shebang, rename script 2026-03-23 23:34:57 +02:00
Copilot e794d7c833 Replace Publish-MacOSBundle.ps1 with a .NET 10 single-file C# script (#1515)
Co-authored-by: Tyrrrz <1935960+Tyrrrz@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-03-23 23:18:46 +02:00
Oleksii Holub 5a26ba0b5a Update WinGet links in Readme 2026-03-21 23:55:12 +02:00
222 changed files with 749 additions and 1258 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links: contact_links:
- name: ⚠ Feature request - name: ⚠ Feature request
url: https://github.com/Tyrrrz/.github/blob/prime/docs/project-status.md url: https://github.com/Tyrrrz/.github/blob/prime/docs/project-status.md
about: Sorry, but this project is in maintenance mode and no longer accepts new feature requests. about: Sorry, but this project is in maintenance mode and currently doesn't accept new feature requests.
- name: 📖 Documentation - name: 📖 Documentation
url: https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs url: https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs
about: Find usage guides and frequently asked questions. about: Find usage guides and frequently asked questions.
+18
View File
@@ -0,0 +1,18 @@
changelog:
exclude:
authors:
- dependabot
- dependabot[bot]
categories:
- title: Enhancements
labels:
- enhancement
- title: Bugs
labels:
- bug
- title: Other
labels:
- "*"
+3 -3
View File
@@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Docker Buildx - name: Install Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Build image - name: Build image
run: > run: >
@@ -37,7 +37,7 @@ jobs:
--output type=tar,dest=DiscordChatExporter.Cli.Docker.tar --output type=tar,dest=DiscordChatExporter.Cli.Docker.tar
- name: Upload image - name: Upload image
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: DiscordChatExporter.Cli.Docker name: DiscordChatExporter.Cli.Docker
path: DiscordChatExporter.Cli.Docker.tar path: DiscordChatExporter.Cli.Docker.tar
@@ -58,7 +58,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Docker Buildx - name: Install Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Login to DockerHub - name: Login to DockerHub
run: > run: >
+7 -7
View File
@@ -29,7 +29,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install .NET - name: Install .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
# Build the project separately to discern between build and format errors # Build the project separately to discern between build and format errors
- name: Build - name: Build
@@ -65,7 +65,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install .NET - name: Install .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
- name: Run tests - name: Run tests
env: env:
@@ -81,7 +81,7 @@ jobs:
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
- name: Upload coverage - name: Upload coverage
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with: with:
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}
@@ -120,7 +120,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install .NET - name: Install .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0 uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
- name: Publish app - name: Publish app
run: > run: >
@@ -135,7 +135,7 @@ jobs:
--self-contained --self-contained
- name: Upload app binaries - name: Upload app binaries
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with: with:
name: ${{ matrix.asset }}.${{ matrix.rid }} name: ${{ matrix.asset }}.${{ matrix.rid }}
path: ${{ matrix.app }}/bin/publish/ path: ${{ matrix.app }}/bin/publish/
@@ -200,7 +200,7 @@ jobs:
steps: steps:
- name: Download app binaries - name: Download app binaries
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with: with:
name: ${{ matrix.asset }}.${{ matrix.rid }} name: ${{ matrix.asset }}.${{ matrix.rid }}
path: ${{ matrix.app }}/ path: ${{ matrix.app }}/
@@ -236,7 +236,7 @@ jobs:
steps: steps:
- name: Notify Discord - name: Notify Discord
uses: tyrrrz/action-http-request@1dd7ad841a34b9299f3741f7c7399f9feefdfb08 # 1.1.3 uses: tyrrrz/action-http-request@110f1a0f0f7e91c3b2de349539249f4573c243fa # 1.1.5
with: with:
url: ${{ secrets.DISCORD_WEBHOOK }} url: ${{ secrets.DISCORD_WEBHOOK }}
method: POST method: POST
+2 -4
View File
@@ -1,14 +1,12 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net10.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Version>999.9.9-dev</Version> <Version>999.9.9-dev</Version>
<Company>Tyrrrz</Company> <Company>Tyrrrz</Company>
<Copyright>Copyright (c) Oleksii Holub</Copyright> <Copyright>Copyright (c) Oleksii Holub</Copyright>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ILLinkTreatWarningsAsErrors>false</ILLinkTreatWarningsAsErrors> <ILLinkTreatWarningsAsErrors>false</ILLinkTreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
</Project>
</Project>
+29 -26
View File
@@ -5,39 +5,42 @@
<ItemGroup> <ItemGroup>
<PackageVersion Include="AngleSharp" Version="1.4.0" /> <PackageVersion Include="AngleSharp" Version="1.4.0" />
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.6.0" /> <PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.8.0" />
<PackageVersion Include="AsyncKeyedLock" Version="8.0.2" /> <PackageVersion Include="AsyncKeyedLock" Version="8.0.2" />
<PackageVersion Include="Avalonia" Version="11.3.12" /> <PackageVersion Include="Avalonia" Version="12.0.4" />
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" /> <PackageVersion Include="Avalonia.Desktop" Version="12.0.4" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" /> <PackageVersion Include="CliFx" Version="3.0.0" />
<PackageVersion Include="CliFx" Version="2.3.6" /> <PackageVersion Include="Cogwheel" Version="2.1.1" />
<PackageVersion Include="Cogwheel" Version="2.1.0" /> <PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" /> <PackageVersion Include="coverlet.collector" Version="10.0.1" />
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
<PackageVersion Include="CSharpier.MsBuild" Version="1.2.6" /> <PackageVersion Include="CSharpier.MsBuild" Version="1.2.6" />
<PackageVersion Include="Deorcify" Version="1.1.0" /> <PackageVersion Include="Deorcify" Version="1.1.0" />
<PackageVersion Include="ThisAssembly.Project" Version="2.1.2" /> <PackageVersion Include="DialogHost.Avalonia" Version="0.12.2" />
<PackageVersion Include="DialogHost.Avalonia" Version="0.10.4" /> <PackageVersion Include="FluentAssertions" Version="8.10.0" />
<PackageVersion Include="FluentAssertions" Version="8.8.0" /> <PackageVersion Include="GitHubActionsTestLogger" Version="3.0.4" />
<PackageVersion Include="GitHubActionsTestLogger" Version="3.0.1" /> <PackageVersion Include="Gress" Version="2.2.0" />
<PackageVersion Include="Gress" Version="2.1.1" />
<PackageVersion Include="JsonExtensions" Version="1.2.0" /> <PackageVersion Include="JsonExtensions" Version="1.2.0" />
<PackageVersion Include="Markdig" Version="1.0.1" /> <PackageVersion Include="Markdig" Version="1.2.0" />
<PackageVersion Include="Material.Avalonia" Version="3.9.2" /> <PackageVersion Include="Material.Avalonia" Version="3.16.1" />
<PackageVersion Include="Material.Icons.Avalonia" Version="2.2.0" /> <PackageVersion Include="Material.Icons.Avalonia" Version="3.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.3" /> <PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.3" /> <PackageVersion
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" /> Include="Microsoft.Extensions.Configuration.EnvironmentVariables"
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" /> Version="10.0.8"
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" /> />
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.6.0" />
<PackageVersion Include="Onova" Version="2.6.13" /> <PackageVersion Include="Onova" Version="2.6.13" />
<PackageVersion Include="Polly" Version="8.6.5" /> <PackageVersion Include="Polly" Version="8.6.6" />
<PackageVersion Include="RazorBlade" Version="0.11.0" /> <PackageVersion Include="PowerKit" Version="1.2.0" />
<PackageVersion Include="Spectre.Console" Version="0.54.0" /> <PackageVersion Include="RazorBlade" Version="1.0.0" />
<PackageVersion Include="Spectre.Console" Version="0.55.2" />
<PackageVersion Include="Superpower" Version="3.1.0" /> <PackageVersion Include="Superpower" Version="3.1.0" />
<PackageVersion Include="WebMarkupMin.Core" Version="2.20.2" /> <PackageVersion Include="ThisAssembly.Project" Version="2.1.2" />
<PackageVersion Include="WebMarkupMin.Core" Version="2.21.0" />
<PackageVersion Include="xunit" Version="2.9.3" /> <PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
<PackageVersion Include="YoutubeExplode" Version="6.5.7" /> <PackageVersion Include="YoutubeExplode" Version="6.6.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -20,11 +20,12 @@
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" /> <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" />
<PackageReference Include="Microsoft.NET.Test.Sdk" /> <PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="PowerKit" PrivateAssets="all" />
<PackageReference Include="xunit" /> <PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" /> <PackageReference Include="xunit.runner.visualstudio" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DiscordChatExporter.Cli\DiscordChatExporter.Cli.csproj" /> <ProjectReference Include="../DiscordChatExporter.Cli/DiscordChatExporter.Cli.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -1,4 +1,4 @@
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
namespace DiscordChatExporter.Cli.Tests.Infra; namespace DiscordChatExporter.Cli.Tests.Infra;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@@ -1,4 +1,4 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
+1 -1
View File
@@ -1,4 +1,4 @@
# DiscordChatExporter Tests # DiscordChatExporter Tests
This test suite runs against a real Discord server, specifically created to exercise different behaviors required by the test scenarios. This test suite runs against a real Discord server, specifically created to exercise different behaviors required by the test scenarios.
In order to run these tests locally, you need to join the test server and configure your authentication token. In order to run these tests locally, you need to join the test server and configure your authentication token.
@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
@@ -1,15 +1,15 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands; using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using FluentAssertions; using FluentAssertions;
using JsonExtensions; using JsonExtensions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -1,15 +1,15 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands; using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Filtering; using DiscordChatExporter.Core.Exporting.Filtering;
using FluentAssertions; using FluentAssertions;
using JsonExtensions; using JsonExtensions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -1,4 +1,4 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
@@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
@@ -8,6 +8,7 @@ using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils; using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using FluentAssertions; using FluentAssertions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -1,11 +1,11 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Utils.Extensions;
using FluentAssertions; using FluentAssertions;
using PowerKit.Extensions;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -1,9 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils.Extensions;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using FluentAssertions; using FluentAssertions;
using PowerKit.Extensions;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -22,7 +22,7 @@ public class HtmlForwardSpecs
// Assert // Assert
message message
.Text() .Text()
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.ContainAll("Forwarded", @"¯\_(ツ)_/¯", "12/29/2025 2:14 PM"); .ContainAll("Forwarded", @"¯\_(ツ)_/¯", "12/29/2025 2:14 PM");
} }
@@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
@@ -8,6 +8,7 @@ using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils; using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using FluentAssertions; using FluentAssertions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -1,9 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils.Extensions;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using FluentAssertions; using FluentAssertions;
using PowerKit.Extensions;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -22,11 +22,14 @@ public class HtmlMarkdownSpecs
// Assert // Assert
message message
.Text() .Text()
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.Contain("Default timestamp: 2/12/2023 1:36 PM"); .Contain("Default timestamp: 2/12/2023 1:36 PM");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -39,8 +42,11 @@ public class HtmlMarkdownSpecs
); );
// Assert // Assert
message.Text().ReplaceWhiteSpace().Should().Contain("Short time timestamp: 1:36 PM"); message.Text().ReplaceWhiteSpace(' ').Should().Contain("Short time timestamp: 1:36 PM");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -53,8 +59,11 @@ public class HtmlMarkdownSpecs
); );
// Assert // Assert
message.Text().ReplaceWhiteSpace().Should().Contain("Long time timestamp: 1:36:12 PM"); message.Text().ReplaceWhiteSpace(' ').Should().Contain("Long time timestamp: 1:36:12 PM");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -67,8 +76,11 @@ public class HtmlMarkdownSpecs
); );
// Assert // Assert
message.Text().ReplaceWhiteSpace().Should().Contain("Short date timestamp: 2/12/2023"); message.Text().ReplaceWhiteSpace(' ').Should().Contain("Short date timestamp: 2/12/2023");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -83,11 +95,14 @@ public class HtmlMarkdownSpecs
// Assert // Assert
message message
.Text() .Text()
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.Contain("Long date timestamp: Sunday, February 12, 2023"); .Contain("Long date timestamp: Sunday, February 12, 2023");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -102,11 +117,14 @@ public class HtmlMarkdownSpecs
// Assert // Assert
message message
.Text() .Text()
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.Contain("Full timestamp: Sunday, February 12, 2023 1:36 PM"); .Contain("Full timestamp: Sunday, February 12, 2023 1:36 PM");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -121,11 +139,14 @@ public class HtmlMarkdownSpecs
// Assert // Assert
message message
.Text() .Text()
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.Contain("Full long timestamp: Sunday, February 12, 2023 1:36:12 PM"); .Contain("Full long timestamp: Sunday, February 12, 2023 1:36:12 PM");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -140,11 +161,14 @@ public class HtmlMarkdownSpecs
// Assert // Assert
message message
.Text() .Text()
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.Contain("Relative timestamp: 2/12/2023 1:36 PM"); .Contain("Relative timestamp: 2/12/2023 1:36 PM");
message.InnerHtml.ReplaceWhiteSpace().Should().Contain("Sunday, February 12, 2023 1:36 PM"); message
.InnerHtml.ReplaceWhiteSpace(' ')
.Should()
.Contain("Sunday, February 12, 2023 1:36 PM");
} }
[Fact] [Fact]
@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using AngleSharp.Dom; using AngleSharp.Dom;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using FluentAssertions; using FluentAssertions;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,13 +1,13 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands; using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using FluentAssertions; using FluentAssertions;
using JsonExtensions; using JsonExtensions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using FluentAssertions; using FluentAssertions;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,12 +1,12 @@
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands; using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Partitioning; using DiscordChatExporter.Core.Exporting.Partitioning;
using FluentAssertions; using FluentAssertions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -17,7 +17,7 @@ public class PartitioningSpecs
public async Task I_can_export_a_channel_with_partitioning_based_on_message_count() public async Task I_can_export_a_channel_with_partitioning_based_on_message_count()
{ {
// Arrange // Arrange
using var dir = TempDir.Create(); using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "output.html"); var filePath = Path.Combine(dir.Path, "output.html");
// Act // Act
@@ -38,7 +38,7 @@ public class PartitioningSpecs
public async Task I_can_export_a_channel_with_partitioning_based_on_file_size() public async Task I_can_export_a_channel_with_partitioning_based_on_file_size()
{ {
// Arrange // Arrange
using var dir = TempDir.Create(); using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "output.html"); var filePath = Path.Combine(dir.Path, "output.html");
// Act // Act
@@ -1,4 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
@@ -1,7 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Cli.Tests.Infra; using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils.Extensions;
using FluentAssertions; using FluentAssertions;
using PowerKit.Extensions;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -16,7 +16,7 @@ public class PlainTextForwardSpecs
// Assert // Assert
document document
.ReplaceWhiteSpace() .ReplaceWhiteSpace(' ')
.Should() .Should()
.ContainAll("{Forwarded Message}", @"¯\_(ツ)_/¯", "12/28/2025 10:52 PM"); .ContainAll("{Forwarded Message}", @"¯\_(ツ)_/¯", "12/28/2025 10:52 PM");
} }
@@ -1,4 +1,4 @@
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Infrastructure; using CliFx.Infrastructure;
@@ -7,6 +7,7 @@ using DiscordChatExporter.Cli.Tests.Infra;
using DiscordChatExporter.Cli.Tests.Utils; using DiscordChatExporter.Cli.Tests.Utils;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using FluentAssertions; using FluentAssertions;
using PowerKit;
using Xunit; using Xunit;
namespace DiscordChatExporter.Cli.Tests.Specs; namespace DiscordChatExporter.Cli.Tests.Specs;
@@ -17,7 +18,7 @@ public class SelfContainedSpecs
public async Task I_can_export_a_channel_and_download_all_referenced_assets() public async Task I_can_export_a_channel_and_download_all_referenced_assets()
{ {
// Arrange // Arrange
using var dir = TempDir.Create(); using var dir = TempDirectory.Create();
var filePath = Path.Combine(dir.Path, "output.html"); var filePath = Path.Combine(dir.Path, "output.html");
// Act // Act
@@ -1,19 +0,0 @@
using System.Text;
namespace DiscordChatExporter.Cli.Tests.Utils.Extensions;
internal static class StringExtensions
{
extension(string str)
{
public string ReplaceWhiteSpace(string replacement = " ")
{
var buffer = new StringBuilder(str.Length);
foreach (var ch in str)
buffer.Append(char.IsWhiteSpace(ch) ? replacement : ch);
return buffer.ToString();
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
using AngleSharp.Html.Dom; using AngleSharp.Html.Dom;
using AngleSharp.Html.Parser; using AngleSharp.Html.Parser;
namespace DiscordChatExporter.Cli.Tests.Utils; namespace DiscordChatExporter.Cli.Tests.Utils;
@@ -1,36 +0,0 @@
using System;
using System.IO;
using System.Reflection;
namespace DiscordChatExporter.Cli.Tests.Utils;
internal partial class TempDir(string path) : IDisposable
{
public string Path { get; } = path;
public void Dispose()
{
try
{
Directory.Delete(Path, true);
}
catch (DirectoryNotFoundException) { }
}
}
internal partial class TempDir
{
public static TempDir Create()
{
var dirPath = System.IO.Path.Combine(
System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
?? Directory.GetCurrentDirectory(),
"Temp",
Guid.NewGuid().ToString()
);
Directory.CreateDirectory(dirPath);
return new TempDir(dirPath);
}
}
@@ -1,37 +0,0 @@
using System;
using System.IO;
using System.Reflection;
namespace DiscordChatExporter.Cli.Tests.Utils;
internal partial class TempFile(string path) : IDisposable
{
public string Path { get; } = path;
public void Dispose()
{
try
{
File.Delete(Path);
}
catch (FileNotFoundException) { }
}
}
internal partial class TempFile
{
public static TempFile Create()
{
var dirPath = System.IO.Path.Combine(
System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
?? Directory.GetCurrentDirectory(),
"Temp"
);
Directory.CreateDirectory(dirPath);
var filePath = System.IO.Path.Combine(dirPath, Guid.NewGuid() + ".tmp");
return new TempFile(filePath);
}
}
@@ -1,4 +1,4 @@
{ {
"$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
"methodDisplayOptions": "all", "methodDisplayOptions": "all",
"methodDisplay": "method" "methodDisplay": "method"
@@ -1,8 +1,8 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx; using CliFx;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
@@ -17,23 +17,22 @@ public abstract class DiscordCommandBase : ICommand
EnvironmentVariable = "DISCORD_TOKEN", EnvironmentVariable = "DISCORD_TOKEN",
Description = "Authentication token." Description = "Authentication token."
)] )]
public required string Token { get; init; } public required string Token { get; set; }
[Obsolete("This option doesn't do anything. Kept for backwards compatibility.")]
[CommandOption( [CommandOption(
"bot", "bot",
'b', 'b',
EnvironmentVariable = "DISCORD_TOKEN_BOT", EnvironmentVariable = "DISCORD_TOKEN_BOT",
Description = "This option doesn't do anything. Kept for backwards compatibility." Description = "This option doesn't do anything. Kept for backwards compatibility."
)] )]
public bool IsBotToken { get; init; } = false; public bool IsBotToken { get; set; } = false;
[CommandOption( [CommandOption(
"respect-rate-limits", "respect-rate-limits",
Description = "Whether to respect advisory rate limits. " Description = "Whether to respect advisory rate limits. "
+ "If disabled, only hard rate limits (i.e. 429 responses) will be respected." + "If disabled, only hard rate limits (i.e. 429 responses) will be respected."
)] )]
public bool ShouldRespectRateLimits { get; init; } = true; public bool ShouldRespectRateLimits { get; set; } = true;
[field: AllowNull, MaybeNull] [field: AllowNull, MaybeNull]
protected DiscordClient Discord => protected DiscordClient Discord =>
@@ -1,12 +1,12 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx;
using CliFx.Exceptions; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Converters; using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Cli.Commands.Shared; using DiscordChatExporter.Cli.Commands.Shared;
@@ -37,23 +37,23 @@ public abstract class ExportCommandBase : DiscordCommandBase
get; get;
// Handle ~/ in paths on Unix systems // Handle ~/ in paths on Unix systems
// https://github.com/Tyrrrz/DiscordChatExporter/pull/903 // https://github.com/Tyrrrz/DiscordChatExporter/pull/903
init => field = Path.GetFullPath(value); set => field = Path.GetFullPath(value);
} = Directory.GetCurrentDirectory(); } = Directory.GetCurrentDirectory();
[CommandOption("format", 'f', Description = "Export format.")] [CommandOption("format", 'f', Description = "Export format.")]
public ExportFormat ExportFormat { get; init; } = ExportFormat.HtmlDark; public ExportFormat ExportFormat { get; set; } = ExportFormat.HtmlDark;
[CommandOption( [CommandOption(
"after", "after",
Description = "Only include messages sent after this date or message ID." Description = "Only include messages sent after this date or message ID."
)] )]
public Snowflake? After { get; init; } public Snowflake? After { get; set; }
[CommandOption( [CommandOption(
"before", "before",
Description = "Only include messages sent before this date or message ID." Description = "Only include messages sent before this date or message ID."
)] )]
public Snowflake? Before { get; init; } public Snowflake? Before { get; set; }
[CommandOption( [CommandOption(
"partition", "partition",
@@ -61,51 +61,51 @@ public abstract class ExportCommandBase : DiscordCommandBase
Description = "Split the output into partitions, each limited to the specified " Description = "Split the output into partitions, each limited to the specified "
+ "number of messages (e.g. '100') or file size (e.g. '10mb')." + "number of messages (e.g. '100') or file size (e.g. '10mb')."
)] )]
public PartitionLimit PartitionLimit { get; init; } = PartitionLimit.Null; public PartitionLimit PartitionLimit { get; set; } = PartitionLimit.Null;
[CommandOption( [CommandOption(
"include-threads", "include-threads",
Description = "Which types of threads should be included.", Description = "Which types of threads should be included.",
Converter = typeof(ThreadInclusionModeBindingConverter) Converter = typeof(ThreadInclusionModeInputConverter)
)] )]
public ThreadInclusionMode ThreadInclusionMode { get; init; } = ThreadInclusionMode.None; public ThreadInclusionMode ThreadInclusionMode { get; set; } = ThreadInclusionMode.None;
[CommandOption( [CommandOption(
"filter", "filter",
Description = "Only include messages that satisfy this filter. " Description = "Only include messages that satisfy this filter. "
+ "See the documentation for more info." + "See the documentation for more info."
)] )]
public MessageFilter MessageFilter { get; init; } = MessageFilter.Null; public MessageFilter MessageFilter { get; set; } = MessageFilter.Null;
[CommandOption( [CommandOption(
"parallel", "parallel",
Description = "Limits how many channels can be exported in parallel." Description = "Limits how many channels can be exported in parallel."
)] )]
public int ParallelLimit { get; init; } = 1; public int ParallelLimit { get; set; } = 1;
[CommandOption( [CommandOption(
"reverse", "reverse",
Description = "Export messages in reverse chronological order (newest first)." Description = "Export messages in reverse chronological order (newest first)."
)] )]
public bool IsReverseMessageOrder { get; init; } public bool IsReverseMessageOrder { get; set; }
[CommandOption( [CommandOption(
"markdown", "markdown",
Description = "Process markdown, mentions, and other special tokens." Description = "Process markdown, mentions, and other special tokens."
)] )]
public bool ShouldFormatMarkdown { get; init; } = true; public bool ShouldFormatMarkdown { get; set; } = true;
[CommandOption( [CommandOption(
"media", "media",
Description = "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)." Description = "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)."
)] )]
public bool ShouldDownloadAssets { get; init; } public bool ShouldDownloadAssets { get; set; }
[CommandOption( [CommandOption(
"reuse-media", "reuse-media",
Description = "Reuse previously downloaded assets to avoid redundant requests." Description = "Reuse previously downloaded assets to avoid redundant requests."
)] )]
public bool ShouldReuseAssets { get; init; } = false; public bool ShouldReuseAssets { get; set; } = false;
[CommandOption( [CommandOption(
"media-dir", "media-dir",
@@ -117,34 +117,33 @@ public abstract class ExportCommandBase : DiscordCommandBase
get; get;
// Handle ~/ in paths on Unix systems // Handle ~/ in paths on Unix systems
// https://github.com/Tyrrrz/DiscordChatExporter/pull/903 // https://github.com/Tyrrrz/DiscordChatExporter/pull/903
init => field = value is not null ? Path.GetFullPath(value) : null; set => field = value is not null ? Path.GetFullPath(value) : null;
} }
[Obsolete("This option doesn't do anything. Kept for backwards compatibility.")]
[CommandOption( [CommandOption(
"dateformat", "dateformat",
Description = "This option doesn't do anything. Kept for backwards compatibility." Description = "This option doesn't do anything. Kept for backwards compatibility."
)] )]
public string DateFormat { get; init; } = "MM/dd/yyyy h:mm tt"; public string DateFormat { get; set; } = "MM/dd/yyyy h:mm tt";
[CommandOption( [CommandOption(
"locale", "locale",
Description = "Locale to use when formatting dates and numbers. " Description = "Locale to use when formatting dates and numbers. "
+ "If not specified, the default system locale will be used." + "If not specified, the default system locale will be used."
)] )]
public string? Locale { get; init; } public string? Locale { get; set; }
[CommandOption("utc", Description = "Normalize all timestamps to UTC+0.")] [CommandOption("utc", Description = "Normalize all timestamps to UTC+0.")]
public bool IsUtcNormalizationEnabled { get; init; } = false; public bool IsUtcNormalizationEnabled { get; set; } = false;
[CommandOption( [CommandOption(
"fuck-russia", "fuck-russia",
EnvironmentVariable = "FUCK_RUSSIA", EnvironmentVariable = "FUCK_RUSSIA",
Description = "Don't print the Support Ukraine message to the console.", Description = "Don't print the Support Ukraine message to the console.",
// Use a converter to accept '1' as 'true' to reuse the existing environment variable // Use a converter to accept '1' as 'true' to reuse the existing environment variable
Converter = typeof(TruthyBooleanBindingConverter) Converter = typeof(TruthyBooleanInputConverter)
)] )]
public bool IsUkraineSupportMessageDisabled { get; init; } = false; public bool IsUkraineSupportMessageDisabled { get; set; } = false;
[field: AllowNull, MaybeNull] [field: AllowNull, MaybeNull]
protected ChannelExporter Exporter => field ??= new ChannelExporter(Discord); protected ChannelExporter Exporter => field ??= new ChannelExporter(Discord);
@@ -1,10 +1,10 @@
using System; using System;
using CliFx.Extensibility; using CliFx.Activation;
using DiscordChatExporter.Cli.Commands.Shared; using DiscordChatExporter.Cli.Commands.Shared;
namespace DiscordChatExporter.Cli.Commands.Converters; namespace DiscordChatExporter.Cli.Commands.Converters;
internal class ThreadInclusionModeBindingConverter : BindingConverter<ThreadInclusionMode> internal class ThreadInclusionModeInputConverter : ScalarInputConverter<ThreadInclusionMode>
{ {
public override ThreadInclusionMode Convert(string? rawValue) public override ThreadInclusionMode Convert(string? rawValue)
{ {
@@ -1,9 +1,9 @@
using System.Globalization; using System.Globalization;
using CliFx.Extensibility; using CliFx.Activation;
namespace DiscordChatExporter.Cli.Commands.Converters; namespace DiscordChatExporter.Cli.Commands.Converters;
internal class TruthyBooleanBindingConverter : BindingConverter<bool> internal class TruthyBooleanInputConverter : ScalarInputConverter<bool>
{ {
public override bool Convert(string? rawValue) public override bool Convert(string? rawValue)
{ {
@@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Utils.Extensions; using DiscordChatExporter.Cli.Utils.Extensions;
@@ -14,23 +14,23 @@ using Spectre.Console;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("exportall", Description = "Exports all accessible channels.")] [Command("exportall", Description = "Exports all accessible channels.")]
public class ExportAllCommand : ExportCommandBase public partial class ExportAllCommand : ExportCommandBase
{ {
[CommandOption("include-dm", Description = "Include direct message channels.")] [CommandOption("include-dm", Description = "Include direct message channels.")]
public bool IncludeDirectChannels { get; init; } = true; public bool IncludeDirectChannels { get; set; } = true;
[CommandOption("include-guilds", Description = "Include server channels.")] [CommandOption("include-guilds", Description = "Include server channels.")]
public bool IncludeGuildChannels { get; init; } = true; public bool IncludeGuildChannels { get; set; } = true;
[CommandOption("include-vc", Description = "Include voice channels.")] [CommandOption("include-vc", Description = "Include voice channels.")]
public bool IncludeVoiceChannels { get; init; } = true; public bool IncludeVoiceChannels { get; set; } = true;
[CommandOption( [CommandOption(
"data-package", "data-package",
Description = "Path to the personal data package (ZIP file) requested from Discord. " Description = "Path to the personal data package (ZIP file) requested from Discord. "
+ "If provided, only channels referenced in the dump will be exported." + "If provided, only channels referenced in the dump will be exported."
)] )]
public string? DataPackageFilePath { get; init; } public string? DataPackageFilePath { get; set; }
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,16 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("export", Description = "Exports one or multiple channels.")] [Command("export", Description = "Exports one or multiple channels.")]
public class ExportChannelsCommand : ExportCommandBase public partial class ExportChannelsCommand : ExportCommandBase
{ {
// TODO: change this to plural (breaking change) // TODO: change this to plural (breaking change)
[CommandOption( [CommandOption(
@@ -19,7 +19,7 @@ public class ExportChannelsCommand : ExportCommandBase
Description = "Channel ID(s). " Description = "Channel ID(s). "
+ "If provided with category ID(s), all channels inside those categories will be exported." + "If provided with category ID(s), all channels inside those categories will be exported."
)] )]
public required IReadOnlyList<Snowflake> ChannelIds { get; init; } public required IReadOnlyList<Snowflake> ChannelIds { get; set; }
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,14 +1,14 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("exportdm", Description = "Exports all direct message channels.")] [Command("exportdm", Description = "Exports all direct message channels.")]
public class ExportDirectMessagesCommand : ExportCommandBase public partial class ExportDirectMessagesCommand : ExportCommandBase
{ {
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Utils.Extensions; using DiscordChatExporter.Cli.Utils.Extensions;
@@ -11,13 +11,13 @@ using Spectre.Console;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("exportguild", Description = "Exports all channels within the specified server.")] [Command("exportguild", Description = "Exports all channels within the specified server.")]
public class ExportGuildCommand : ExportCommandBase public partial class ExportGuildCommand : ExportCommandBase
{ {
[CommandOption("guild", 'g', Description = "Server ID.")] [CommandOption("guild", 'g', Description = "Server ID.")]
public required Snowflake GuildId { get; init; } public required Snowflake GuildId { get; set; }
[CommandOption("include-vc", Description = "Include voice channels.")] [CommandOption("include-vc", Description = "Include voice channels.")]
public bool IncludeVoiceChannels { get; init; } = true; public bool IncludeVoiceChannels { get; set; } = true;
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,31 +1,31 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Cli.Commands.Converters; using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Cli.Commands.Shared; using DiscordChatExporter.Cli.Commands.Shared;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("channels", Description = "Get the list of channels in a server.")] [Command("channels", Description = "Get the list of channels in a server.")]
public class GetChannelsCommand : DiscordCommandBase public partial class GetChannelsCommand : DiscordCommandBase
{ {
[CommandOption("guild", 'g', Description = "Server ID.")] [CommandOption("guild", 'g', Description = "Server ID.")]
public required Snowflake GuildId { get; init; } public required Snowflake GuildId { get; set; }
[CommandOption("include-vc", Description = "Include voice channels.")] [CommandOption("include-vc", Description = "Include voice channels.")]
public bool IncludeVoiceChannels { get; init; } = true; public bool IncludeVoiceChannels { get; set; } = true;
[CommandOption( [CommandOption(
"include-threads", "include-threads",
Description = "Which types of threads should be included.", Description = "Which types of threads should be included.",
Converter = typeof(ThreadInclusionModeBindingConverter) Converter = typeof(ThreadInclusionModeInputConverter)
)] )]
public ThreadInclusionMode ThreadInclusionMode { get; init; } = ThreadInclusionMode.None; public ThreadInclusionMode ThreadInclusionMode { get; set; } = ThreadInclusionMode.None;
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,16 +1,16 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("dm", Description = "Gets the list of all direct message channels.")] [Command("dm", Description = "Gets the list of all direct message channels.")]
public class GetDirectChannelsCommand : DiscordCommandBase public partial class GetDirectChannelsCommand : DiscordCommandBase
{ {
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,16 +1,16 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using DiscordChatExporter.Cli.Commands.Base; using DiscordChatExporter.Cli.Commands.Base;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("guilds", Description = "Gets the list of accessible servers.")] [Command("guilds", Description = "Gets the list of accessible servers.")]
public class GetGuildsCommand : DiscordCommandBase public partial class GetGuildsCommand : DiscordCommandBase
{ {
public override async ValueTask ExecuteAsync(IConsole console) public override async ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,13 +1,13 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx; using CliFx;
using CliFx.Attributes; using CliFx.Binding;
using CliFx.Infrastructure; using CliFx.Infrastructure;
namespace DiscordChatExporter.Cli.Commands; namespace DiscordChatExporter.Cli.Commands;
[Command("guide", Description = "Explains how to obtain the token, server or channel ID.")] [Command("guide", Description = "Explains how to obtain the token, server or channel ID.")]
public class GuideCommand : ICommand public partial class GuideCommand : ICommand
{ {
public ValueTask ExecuteAsync(IConsole console) public ValueTask ExecuteAsync(IConsole console)
{ {
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Cli.Commands.Shared; namespace DiscordChatExporter.Cli.Commands.Shared;
public enum ThreadInclusionMode public enum ThreadInclusionMode
{ {
@@ -1,26 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<ApplicationIcon>..\favicon.ico</ApplicationIcon> <ApplicationIcon>../favicon.ico</ApplicationIcon>
<PublishTrimmed>true</PublishTrimmed> <PublishTrimmed>true</PublishTrimmed>
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory> <CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
</PropertyGroup> </PropertyGroup>
<!-- HACK: Disable trim warnings because they seem to break when the code contains C# 14 extension blocks -->
<PropertyGroup>
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
<EnableAotAnalyzer>false</EnableAotAnalyzer>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CliFx" /> <PackageReference Include="CliFx" />
<PackageReference Include="CSharpier.MsBuild" PrivateAssets="all" /> <PackageReference Include="CSharpier.MsBuild" PrivateAssets="all" />
<PackageReference Include="Deorcify" PrivateAssets="all" /> <PackageReference Include="Deorcify" PrivateAssets="all" />
<PackageReference Include="Gress" /> <PackageReference Include="Gress" />
<PackageReference Include="PowerKit" PrivateAssets="all" />
<PackageReference Include="Spectre.Console" /> <PackageReference Include="Spectre.Console" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\DiscordChatExporter.Core\DiscordChatExporter.Core.csproj" /> <ProjectReference Include="../DiscordChatExporter.Core/DiscordChatExporter.Core.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+2 -30
View File
@@ -1,41 +1,13 @@
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx; using CliFx;
using DiscordChatExporter.Cli.Commands;
using DiscordChatExporter.Cli.Commands.Converters;
using DiscordChatExporter.Core.Exporting.Filtering;
using DiscordChatExporter.Core.Exporting.Partitioning;
namespace DiscordChatExporter.Cli; namespace DiscordChatExporter.Cli;
public static class Program public static class Program
{ {
// Explicit references because CliFx relies on reflection and we're publishing with trimming enabled
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportAllCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportChannelsCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportDirectMessagesCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportGuildCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GetChannelsCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GetDirectChannelsCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GetGuildsCommand))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GuideCommand))]
[DynamicDependency(
DynamicallyAccessedMemberTypes.All,
typeof(ThreadInclusionModeBindingConverter)
)]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TruthyBooleanBindingConverter))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PartitionLimit))]
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(MessageFilter))]
public static async Task<int> Main(string[] args) => public static async Task<int> Main(string[] args) =>
await new CliApplicationBuilder() await new CommandLineApplicationBuilder()
.AddCommand<ExportAllCommand>() .AddCommandsFromThisAssembly()
.AddCommand<ExportChannelsCommand>()
.AddCommand<ExportDirectMessagesCommand>()
.AddCommand<ExportGuildCommand>()
.AddCommand<GetChannelsCommand>()
.AddCommand<GetDirectChannelsCommand>()
.AddCommand<GetGuildsCommand>()
.AddCommand<GuideCommand>()
.Build() .Build()
.RunAsync(args); .RunAsync(args);
} }
@@ -1,4 +1,4 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using CliFx.Infrastructure; using CliFx.Infrastructure;
using Spectre.Console; using Spectre.Console;
@@ -1,6 +1,6 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
using System; using System;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,9 +1,9 @@
using System; using System;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/channel#channel-object-channel-types // https://discord.com/developers/docs/resources/channel#channel-object-channel-types
public enum ChannelKind public enum ChannelKind
@@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Discord.Data.Common; namespace DiscordChatExporter.Core.Discord.Data.Common;
public interface IHasId public interface IHasId
{ {
@@ -1,4 +1,4 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -3,8 +3,8 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data.Embeds; namespace DiscordChatExporter.Core.Discord.Data.Embeds;
@@ -47,9 +47,7 @@ public partial record Embed
var kind = var kind =
json.GetPropertyOrNull("type") json.GetPropertyOrNull("type")
?.GetStringOrNull() ?.GetStringOrNull()
?.Pipe(s => .Pipe(s => Enum.ParseOrNull<EmbedKind>(s, true))
Enum.TryParse<EmbedKind>(s, true, out var result) ? result : (EmbedKind?)null
)
?? EmbedKind.Rich; ?? EmbedKind.Rich;
var url = json.GetPropertyOrNull("url")?.GetNonWhiteSpaceStringOrNull(); var url = json.GetPropertyOrNull("url")?.GetNonWhiteSpaceStringOrNull();
@@ -58,7 +56,7 @@ public partial record Embed
var color = json.GetPropertyOrNull("color") var color = json.GetPropertyOrNull("color")
?.GetInt32OrNull() ?.GetInt32OrNull()
?.Pipe(System.Drawing.Color.FromArgb) ?.Pipe(System.Drawing.Color.FromArgb)
.ResetAlpha(); .WithFullAlpha();
var author = json.GetPropertyOrNull("author")?.Pipe(EmbedAuthor.Parse); var author = json.GetPropertyOrNull("author")?.Pipe(EmbedAuthor.Parse);
var description = json.GetPropertyOrNull("description")?.GetStringOrNull(); var description = json.GetPropertyOrNull("description")?.GetStringOrNull();
@@ -1,4 +1,4 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Discord.Data.Embeds; namespace DiscordChatExporter.Core.Discord.Data.Embeds;
@@ -1,4 +1,4 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Discord.Data.Embeds; namespace DiscordChatExporter.Core.Discord.Data.Embeds;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Discord.Data.Embeds; namespace DiscordChatExporter.Core.Discord.Data.Embeds;
public partial record YouTubeVideoEmbedProjection(string VideoId) public partial record YouTubeVideoEmbedProjection(string VideoId)
{ {
@@ -1,8 +1,8 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,6 +1,6 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,9 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Discord.Data.Embeds; using DiscordChatExporter.Core.Discord.Data.Embeds;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
using System; using System;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/channel#message-object-message-types // https://discord.com/developers/docs/resources/channel#message-object-message-types
public enum MessageKind public enum MessageKind
@@ -1,6 +1,6 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,5 +1,5 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,8 +1,8 @@
using System.Drawing; using System.Drawing;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -18,7 +18,7 @@ public record Role(Snowflake Id, string Name, int Position, Color? Color) : IHas
var color = json.GetPropertyOrNull("color") var color = json.GetPropertyOrNull("color")
?.GetInt32OrNull() ?.GetInt32OrNull()
?.Pipe(System.Drawing.Color.FromArgb) ?.Pipe(System.Drawing.Color.FromArgb)
.ResetAlpha() .WithFullAlpha()
.NullIf(c => c.ToRgb() <= 0); .NullIf(c => c.ToRgb() <= 0);
return new Role(id, name, position, color); return new Role(id, name, position, color);
@@ -1,8 +1,8 @@
using System; using System;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
public enum StickerFormat public enum StickerFormat
{ {
@@ -1,7 +1,7 @@
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord.Data; namespace DiscordChatExporter.Core.Discord.Data;
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@@ -11,10 +11,10 @@ using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exceptions; using DiscordChatExporter.Core.Exceptions;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils.Extensions;
using Gress; using Gress;
using JsonExtensions.Http; using JsonExtensions.Http;
using JsonExtensions.Reading; using JsonExtensions.Reading;
using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Discord; namespace DiscordChatExporter.Core.Discord;
@@ -59,12 +59,12 @@ public class DiscordClient(
{ {
var remainingRequestCount = response var remainingRequestCount = response
.Headers.TryGetValue("X-RateLimit-Remaining") .Headers.TryGetValue("X-RateLimit-Remaining")
?.Pipe(s => int.Parse(s, CultureInfo.InvariantCulture)); ?.Pipe(s => int.ParseOrNull(s, CultureInfo.InvariantCulture));
var resetAfterDelay = response var resetAfterDelay = response
.Headers.TryGetValue("X-RateLimit-Reset-After") .Headers.TryGetValue("X-RateLimit-Reset-After")
?.Pipe(s => double.Parse(s, CultureInfo.InvariantCulture)) ?.Pipe(s => double.ParseOrNull(s, CultureInfo.InvariantCulture))
.Pipe(TimeSpan.FromSeconds); ?.Pipe(TimeSpan.FromSeconds);
// If this was the last request available before hitting the rate limit, // If this was the last request available before hitting the rate limit,
// wait out the reset time so that future requests can succeed. // wait out the reset time so that future requests can succeed.
@@ -161,7 +161,7 @@ public class DiscordClient(
$""" $"""
Request to '{url}' failed: {response Request to '{url}' failed: {response
.StatusCode.ToString() .StatusCode.ToString()
.ToSpaceSeparatedWords() .SeparateWords(' ')
.ToLowerInvariant()}. .ToLowerInvariant()}.
Response content: {await response.Content.ReadAsStringAsync( Response content: {await response.Content.ReadAsStringAsync(
cancellationToken cancellationToken
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
@@ -1,3 +1,3 @@
namespace DiscordChatExporter.Core.Discord.Dump; namespace DiscordChatExporter.Core.Discord.Dump;
public record DataDumpChannel(Snowflake Id, string Name); public record DataDumpChannel(Snowflake Id, string Name);
@@ -1,4 +1,4 @@
using System; using System;
namespace DiscordChatExporter.Core.Discord; namespace DiscordChatExporter.Core.Discord;
@@ -1,4 +1,4 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Discord; namespace DiscordChatExporter.Core.Discord;
public enum TokenKind public enum TokenKind
{ {
@@ -6,6 +6,7 @@
<PackageReference Include="Gress" /> <PackageReference Include="Gress" />
<PackageReference Include="JsonExtensions" /> <PackageReference Include="JsonExtensions" />
<PackageReference Include="Polly" /> <PackageReference Include="Polly" />
<PackageReference Include="PowerKit" PrivateAssets="all" />
<PackageReference Include="RazorBlade" /> <PackageReference Include="RazorBlade" />
<PackageReference Include="Superpower" /> <PackageReference Include="Superpower" />
<PackageReference Include="WebMarkupMin.Core" /> <PackageReference Include="WebMarkupMin.Core" />
@@ -1,4 +1,4 @@
using System; using System;
namespace DiscordChatExporter.Core.Exceptions; namespace DiscordChatExporter.Core.Exceptions;
@@ -1,4 +1,4 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
@@ -1,11 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
@@ -1,15 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web; using System.Web;
using AsyncKeyedLock; using AsyncKeyedLock;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
@@ -65,7 +65,14 @@ internal partial class ExportAssetDownloader(string workingDirPath, bool reuse)
async innerCancellationToken => async innerCancellationToken =>
{ {
// Download the file // Download the file
using var response = await Http.Client.GetAsync(url, innerCancellationToken); using var response = await Http.Client.GetAsync(
url,
HttpCompletionOption.ResponseHeadersRead,
innerCancellationToken
);
response.EnsureSuccessStatusCode();
await using var output = File.Create(filePath); await using var output = File.Create(filePath);
await response.Content.CopyToAsync(output, innerCancellationToken); await response.Content.CopyToAsync(output, innerCancellationToken);
}, },
@@ -96,7 +103,7 @@ internal partial class ExportAssetDownloader
private static string GetFileNameFromUrl(string url, string urlHash) private static string GetFileNameFromUrl(string url, string urlHash)
{ {
// Try to extract the file name from URL // Try to extract the file name from URL
var fileName = Regex.Match(url, @".+/([^?]*)").Groups[1].Value; var fileName = new Uri(url, UriKind.RelativeOrAbsolute).TryGetFileName();
// If it's not there, just use the URL hash as the file name // If it's not there, just use the URL hash as the file name
if (string.IsNullOrWhiteSpace(fileName)) if (string.IsNullOrWhiteSpace(fileName))
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
@@ -9,7 +9,7 @@ using System.Threading.Tasks;
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
@@ -1,4 +1,4 @@
using System; using System;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
@@ -1,4 +1,4 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
@@ -7,7 +7,7 @@ using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
using DiscordChatExporter.Core.Exporting.Filtering; using DiscordChatExporter.Core.Exporting.Filtering;
using DiscordChatExporter.Core.Exporting.Partitioning; using DiscordChatExporter.Core.Exporting.Partitioning;
using DiscordChatExporter.Core.Utils.Extensions; using PowerKit.Extensions;
namespace DiscordChatExporter.Core.Exporting; namespace DiscordChatExporter.Core.Exporting;
@@ -1,4 +1,4 @@
namespace DiscordChatExporter.Core.Exporting.Filtering; namespace DiscordChatExporter.Core.Exporting.Filtering;
internal enum BinaryExpressionKind internal enum BinaryExpressionKind
{ {
@@ -1,4 +1,4 @@
using System; using System;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;
namespace DiscordChatExporter.Core.Exporting.Filtering; namespace DiscordChatExporter.Core.Exporting.Filtering;
@@ -1,4 +1,4 @@
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using DiscordChatExporter.Core.Discord.Data; using DiscordChatExporter.Core.Discord.Data;

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