mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-08 23:24:39 +02:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 31de8a5b06 | |||
| efd430ed29 | |||
| 623c688e1b | |||
| 05f8df51e9 | |||
| e2c633b004 | |||
| b11a57a825 | |||
| f502e577c2 | |||
| 8bc9fe7c72 | |||
| 97485c280b | |||
| 5a57c4202d | |||
| acac8f7bbb | |||
| 5632d6c386 | |||
| f4d1e630f7 | |||
| 799dd1ad81 | |||
| 64a4ba296e | |||
| f0e99765f7 | |||
| 45629c9d7f | |||
| 2aec84884b | |||
| 6258394fc0 | |||
| e6472543e0 | |||
| ee163d6de7 | |||
| 5838c0546e | |||
| 07a02773be | |||
| 0a623b24d1 | |||
| da4f5ea74a | |||
| 34b5ccc75b | |||
| 7456f0fe3a | |||
| 757daa7a60 | |||
| 9590d4461a | |||
| a27fc3e72e | |||
| f10bba63e6 | |||
| b76b42549b | |||
| ae70fe9741 | |||
| af3ec8c299 | |||
| f1673cde77 | |||
| ed33d19bc0 | |||
| 80e9ad75ba | |||
| 295f4cf9a2 | |||
| 1f189e5206 | |||
| df683b3b41 | |||
| d467948d0b | |||
| f6166764e9 | |||
| 7ee2763d4b | |||
| eb99ab29ed | |||
| 5c61306231 | |||
| 2e4601d378 | |||
| cc703cc860 | |||
| 661f82c478 | |||
| b9f19f7003 | |||
| e794d7c833 | |||
| 5a26ba0b5a |
@@ -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.
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
changelog:
|
||||||
|
exclude:
|
||||||
|
authors:
|
||||||
|
- dependabot
|
||||||
|
- dependabot[bot]
|
||||||
|
|
||||||
|
categories:
|
||||||
|
- title: Enhancements
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
|
||||||
|
- title: Bugs
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
|
||||||
|
- title: Other
|
||||||
|
labels:
|
||||||
|
- "*"
|
||||||
@@ -23,10 +23,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- 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
|
||||||
@@ -55,10 +55,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- 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: >
|
||||||
|
|||||||
+10
-10
@@ -26,10 +26,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Install .NET
|
- name: Install .NET
|
||||||
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.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
|
||||||
@@ -62,10 +62,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Install .NET
|
- name: Install .NET
|
||||||
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.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@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
@@ -117,10 +117,10 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
|
|
||||||
- name: Install .NET
|
- name: Install .NET
|
||||||
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
uses: actions/setup-dotnet@26b0ec14cb23fa6904739307f278c14f94c95bf1 # v5.4.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
|
||||||
|
|||||||
@@ -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>
|
|
||||||
|
|||||||
+33
-30
@@ -4,40 +4,43 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="AngleSharp" Version="1.4.0" />
|
<PackageVersion Include="AngleSharp" Version="1.5.1" />
|
||||||
<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.5" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageVersion Include="Avalonia.Desktop" Version="12.0.5" />
|
||||||
<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.3.0" />
|
||||||
<PackageVersion Include="CSharpier.MsBuild" Version="1.2.6" />
|
<PackageVersion Include="Deorcify" Version="2.0.1" />
|
||||||
<PackageVersion Include="Deorcify" Version="1.1.0" />
|
<PackageVersion Include="DialogHost.Avalonia" Version="0.12.2" />
|
||||||
<PackageVersion Include="ThisAssembly.Project" Version="2.1.2" />
|
<PackageVersion Include="FluentAssertions" Version="8.10.0" />
|
||||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.10.4" />
|
<PackageVersion Include="GitHubActionsTestLogger" Version="3.0.4" />
|
||||||
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
|
<PackageVersion Include="Gress" Version="2.2.0" />
|
||||||
<PackageVersion Include="GitHubActionsTestLogger" Version="3.0.1" />
|
<PackageVersion Include="JsonExtensions" Version="1.2.3" />
|
||||||
<PackageVersion Include="Gress" Version="2.1.1" />
|
<PackageVersion Include="Markdig" Version="1.3.2" />
|
||||||
<PackageVersion Include="JsonExtensions" Version="1.2.0" />
|
<PackageVersion Include="Material.Avalonia" Version="3.16.1" />
|
||||||
<PackageVersion Include="Markdig" Version="1.0.1" />
|
<PackageVersion Include="Material.Icons.Avalonia" Version="3.0.2" />
|
||||||
<PackageVersion Include="Material.Avalonia" Version="3.9.2" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.9" />
|
||||||
<PackageVersion Include="Material.Icons.Avalonia" Version="2.2.0" />
|
<PackageVersion
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
|
Include="Microsoft.Extensions.Configuration.EnvironmentVariables"
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.3" />
|
Version="10.0.9"
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
|
/>
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.9" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
|
||||||
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.7.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.7.0" />
|
||||||
<PackageVersion Include="RazorBlade" Version="0.11.0" />
|
<PackageVersion Include="PowerKit" Version="2.0.2" />
|
||||||
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
|
<PackageVersion Include="RazorBlade" Version="1.0.0" />
|
||||||
|
<PackageVersion Include="Spectre.Console" Version="0.57.1" />
|
||||||
<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>
|
||||||
|
|||||||
@@ -14,17 +14,18 @@
|
|||||||
<PackageReference Include="coverlet.collector" PrivateAssets="all" />
|
<PackageReference Include="coverlet.collector" PrivateAssets="all" />
|
||||||
<PackageReference Include="CSharpier.MsBuild" PrivateAssets="all" />
|
<PackageReference Include="CSharpier.MsBuild" PrivateAssets="all" />
|
||||||
<PackageReference Include="FluentAssertions" />
|
<PackageReference Include="FluentAssertions" />
|
||||||
<PackageReference Include="GitHubActionsTestLogger" PrivateAssets="all" />
|
<PackageReference Include="GitHubActionsTestLogger" />
|
||||||
<PackageReference Include="JsonExtensions" />
|
<PackageReference Include="JsonExtensions" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
<PackageReference Include="Microsoft.Extensions.Configuration" />
|
||||||
<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" />
|
||||||
<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,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,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);
|
||||||
@@ -166,6 +165,37 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||||||
throw new CommandException("Option --media-dir cannot be used without --media.");
|
throw new CommandException("Option --media-dir cannot be used without --media.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure the user does not try to export multiple channels into one file.
|
||||||
|
// Output path must either be a directory or contain template tokens for this to work.
|
||||||
|
// Validate this up-front, before fetching threads, because thread fetching can take a
|
||||||
|
// long time and it's frustrating to fail only after it completes.
|
||||||
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/799
|
||||||
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/917
|
||||||
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1549
|
||||||
|
var mayExportMultipleChannels =
|
||||||
|
// Multiple channels were provided explicitly
|
||||||
|
channels.Count > 1
|
||||||
|
// Thread inclusion can add more channels to the export
|
||||||
|
|| ThreadInclusionMode != ThreadInclusionMode.None;
|
||||||
|
|
||||||
|
var isValidOutputPath =
|
||||||
|
// Anything is valid when exporting a single channel
|
||||||
|
!mayExportMultipleChannels
|
||||||
|
// When using template tokens, assume the user knows what they're doing
|
||||||
|
|| OutputPath.Contains('%')
|
||||||
|
// Otherwise, require an existing directory or an unambiguous directory path
|
||||||
|
|| Directory.Exists(OutputPath)
|
||||||
|
|| Path.EndsInDirectorySeparator(OutputPath);
|
||||||
|
|
||||||
|
if (!isValidOutputPath)
|
||||||
|
{
|
||||||
|
throw new CommandException(
|
||||||
|
"Attempted to export multiple channels, but the output path is neither a directory nor a template. "
|
||||||
|
+ "If the provided output path is meant to be treated as a directory, make sure it ends with a slash. "
|
||||||
|
+ $"Provided output path: '{OutputPath}'."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
var unwrappedChannels = new List<Channel>(channels);
|
var unwrappedChannels = new List<Channel>(channels);
|
||||||
|
|
||||||
// Unwrap threads
|
// Unwrap threads
|
||||||
@@ -206,28 +236,6 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||||||
await console.Output.WriteLineAsync($"Fetched {fetchedThreadsCount} thread(s).");
|
await console.Output.WriteLineAsync($"Fetched {fetchedThreadsCount} thread(s).");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the user does not try to export multiple channels into one file.
|
|
||||||
// Output path must either be a directory or contain template tokens for this to work.
|
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/799
|
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/917
|
|
||||||
var isValidOutputPath =
|
|
||||||
// Anything is valid when exporting a single channel
|
|
||||||
unwrappedChannels.Count <= 1
|
|
||||||
// When using template tokens, assume the user knows what they're doing
|
|
||||||
|| OutputPath.Contains('%')
|
|
||||||
// Otherwise, require an existing directory or an unambiguous directory path
|
|
||||||
|| Directory.Exists(OutputPath)
|
|
||||||
|| Path.EndsInDirectorySeparator(OutputPath);
|
|
||||||
|
|
||||||
if (!isValidOutputPath)
|
|
||||||
{
|
|
||||||
throw new CommandException(
|
|
||||||
"Attempted to export multiple channels, but the output path is neither a directory nor a template. "
|
|
||||||
+ "If the provided output path is meant to be treated as a directory, make sure it ends with a slash. "
|
|
||||||
+ $"Provided output path: '{OutputPath}'."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
var errorsByChannel = new ConcurrentDictionary<Channel, string>();
|
var errorsByChannel = new ConcurrentDictionary<Channel, string>();
|
||||||
var warningsByChannel = new ConcurrentDictionary<Channel, string>();
|
var warningsByChannel = new ConcurrentDictionary<Channel, string>();
|
||||||
|
|||||||
+3
-3
@@ -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)
|
||||||
{
|
{
|
||||||
+3
-3
@@ -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" />
|
||||||
<PackageReference Include="Gress" />
|
<PackageReference Include="Gress" />
|
||||||
|
<PackageReference Include="PowerKit" />
|
||||||
<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>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -13,4 +13,5 @@ public enum MessageKind
|
|||||||
GuildMemberJoin = 7,
|
GuildMemberJoin = 7,
|
||||||
ThreadCreated = 18,
|
ThreadCreated = 18,
|
||||||
Reply = 19,
|
Reply = 19,
|
||||||
|
ThreadStarterMessage = 21,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -30,9 +30,8 @@ public class DiscordClient(
|
|||||||
string url,
|
string url,
|
||||||
TokenKind tokenKind,
|
TokenKind tokenKind,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
)
|
) =>
|
||||||
{
|
await Http.ResponseResiliencePipeline.ExecuteAsync(
|
||||||
return await Http.ResponseResiliencePipeline.ExecuteAsync(
|
|
||||||
async innerCancellationToken =>
|
async innerCancellationToken =>
|
||||||
{
|
{
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url));
|
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url));
|
||||||
@@ -59,12 +58,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.
|
||||||
@@ -91,7 +90,6 @@ public class DiscordClient(
|
|||||||
},
|
},
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
private async ValueTask<TokenKind> ResolveTokenKindAsync(
|
private async ValueTask<TokenKind> ResolveTokenKindAsync(
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
@@ -161,7 +159,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
|
||||||
@@ -364,6 +362,7 @@ public class DiscordClient(
|
|||||||
$"guilds/{guildId}/members/{memberId}",
|
$"guilds/{guildId}/members/{memberId}",
|
||||||
cancellationToken
|
cancellationToken
|
||||||
);
|
);
|
||||||
|
|
||||||
return response?.Pipe(j => Member.Parse(j, guildId));
|
return response?.Pipe(j => Member.Parse(j, guildId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,14 +411,12 @@ public class DiscordClient(
|
|||||||
?.GetNonWhiteSpaceStringOrNull()
|
?.GetNonWhiteSpaceStringOrNull()
|
||||||
?.Pipe(Snowflake.Parse);
|
?.Pipe(Snowflake.Parse);
|
||||||
|
|
||||||
Channel? parent = null;
|
// It's possible for the parent channel to be inaccessible, despite the
|
||||||
if (parentId is not null)
|
// child channel being accessible.
|
||||||
{
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1108
|
||||||
// It's possible for the parent channel to be inaccessible, despite the
|
var parent = parentId is not null
|
||||||
// child channel being accessible.
|
? await TryGetChannelAsync(parentId.Value, cancellationToken)
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1108
|
: null;
|
||||||
parent = await TryGetChannelAsync(parentId.Value, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Channel.Parse(response.Value, parent);
|
return Channel.Parse(response.Value, parent);
|
||||||
}
|
}
|
||||||
@@ -607,8 +604,12 @@ public class DiscordClient(
|
|||||||
.SetQueryParameter("after", (after ?? Snowflake.Zero).ToString())
|
.SetQueryParameter("after", (after ?? Snowflake.Zero).ToString())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var response = await GetJsonResponseAsync(url, cancellationToken);
|
// Can be null on channels that the user cannot access
|
||||||
var message = response.EnumerateArray().Select(Message.Parse).FirstOrDefault();
|
var response = await TryGetJsonResponseAsync(url, cancellationToken);
|
||||||
|
if (response is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var message = response.Value.EnumerateArray().Select(Message.Parse).FirstOrDefault();
|
||||||
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@@ -625,8 +626,39 @@ public class DiscordClient(
|
|||||||
.SetQueryParameter("before", before?.ToString())
|
.SetQueryParameter("before", before?.ToString())
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var response = await GetJsonResponseAsync(url, cancellationToken);
|
// Can be null on channels that the user cannot access
|
||||||
return response.EnumerateArray().Select(Message.Parse).LastOrDefault();
|
var response = await TryGetJsonResponseAsync(url, cancellationToken);
|
||||||
|
if (response is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return response.Value.EnumerateArray().Select(Message.Parse).LastOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask<Message?> TryGetMessageAsync(
|
||||||
|
Snowflake channelId,
|
||||||
|
Snowflake messageId,
|
||||||
|
CancellationToken cancellationToken = default
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Use the regular message listing endpoint with the 'around' parameter instead of the
|
||||||
|
// dedicated single-message endpoint, because the latter is not accessible to user tokens.
|
||||||
|
var url = new UrlBuilder()
|
||||||
|
.SetPath($"channels/{channelId}/messages")
|
||||||
|
.SetQueryParameter("around", messageId.ToString())
|
||||||
|
.SetQueryParameter("limit", "1")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Can be null on channels that the user cannot access
|
||||||
|
var response = await TryGetJsonResponseAsync(url, cancellationToken);
|
||||||
|
if (response is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// The endpoint returns messages around the requested ID, so make sure to only return
|
||||||
|
// the message that exactly matches it (it may be absent if it has been deleted).
|
||||||
|
return response
|
||||||
|
.Value.EnumerateArray()
|
||||||
|
.Select(Message.Parse)
|
||||||
|
.FirstOrDefault(m => m.Id == messageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<Message> GetMessagesAsync(
|
public async IAsyncEnumerable<Message> GetMessagesAsync(
|
||||||
@@ -701,7 +733,21 @@ public class DiscordClient(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return message;
|
// Some messages, for example thread starter messages, are returned by the API as content-less references.
|
||||||
|
// Try to resolve them to the actual message so that they appear as they do in the Discord client.
|
||||||
|
var actualMessage =
|
||||||
|
message.Kind == MessageKind.ThreadStarterMessage
|
||||||
|
&& message.Reference?.ChannelId is { } referencedChannelId
|
||||||
|
&& message.Reference?.MessageId is { } referencedMessageId
|
||||||
|
? await TryGetMessageAsync(
|
||||||
|
referencedChannelId,
|
||||||
|
referencedMessageId,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
yield return actualMessage ?? message;
|
||||||
|
|
||||||
currentAfter = message.Id;
|
currentAfter = message.Id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -769,7 +815,20 @@ public class DiscordClient(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return message;
|
// Some messages, for example thread starter messages, are returned by the API as content-less references.
|
||||||
|
// Try to resolve them to the actual message so that they appear as they do in the Discord client.
|
||||||
|
var actualMessage =
|
||||||
|
message.Kind == MessageKind.ThreadStarterMessage
|
||||||
|
&& message.Reference?.ChannelId is { } referencedChannelId
|
||||||
|
&& message.Reference?.MessageId is { } referencedMessageId
|
||||||
|
? await TryGetMessageAsync(
|
||||||
|
referencedChannelId,
|
||||||
|
referencedMessageId,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
yield return actualMessage ?? message;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBefore = messages.Last().Id;
|
currentBefore = messages.Last().Id;
|
||||||
|
|||||||
@@ -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" />
|
||||||
<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;
|
||||||
|
|
||||||
@@ -38,23 +38,28 @@ internal partial class ExportAssetDownloader(string workingDirPath, bool reuse)
|
|||||||
return _previousPathsByUrl[url] = filePath;
|
return _previousPathsByUrl[url] = filePath;
|
||||||
|
|
||||||
// Check for a file cached by the legacy naming scheme (5-char hash) and rename it
|
// Check for a file cached by the legacy naming scheme (5-char hash) and rename it
|
||||||
// to the new naming scheme to preserve backwards compatibility with existing exports
|
// to the new naming scheme to preserve backwards compatibility with existing exports.
|
||||||
|
// This will catch both the 5-char lowercase hash and the 5-char uppercase hash variants.
|
||||||
if (reuse)
|
if (reuse)
|
||||||
{
|
{
|
||||||
var legacyFilePath = Path.Combine(workingDirPath, GetLegacyFileNameFromUrl(url));
|
var legacyFileNames = GetLegacyFileNamesFromUrl(url);
|
||||||
if (File.Exists(legacyFilePath))
|
foreach (var legacyFileName in legacyFileNames)
|
||||||
{
|
{
|
||||||
// Overwrite in case the destination file was created concurrently between our
|
var legacyFilePath = Path.Combine(workingDirPath, legacyFileName);
|
||||||
// earlier existence check and this move operation
|
if (File.Exists(legacyFilePath))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
File.Move(legacyFilePath, filePath, overwrite: true);
|
// Overwrite in case the destination file was created concurrently between our
|
||||||
return _previousPathsByUrl[url] = filePath;
|
// earlier existence check and this move operation
|
||||||
}
|
try
|
||||||
catch (IOException)
|
{
|
||||||
{
|
File.Move(legacyFilePath, filePath, true);
|
||||||
// The legacy file was moved or deleted concurrently or something else happened.
|
return _previousPathsByUrl[url] = filePath;
|
||||||
// Upgrading old files is not crucial, so we can just move on.
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
// The legacy file was moved or deleted concurrently or something else happened.
|
||||||
|
// Upgrading old files is not crucial, so we can just move on.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +70,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);
|
||||||
},
|
},
|
||||||
@@ -80,10 +92,16 @@ internal partial class ExportAssetDownloader
|
|||||||
{
|
{
|
||||||
private static string NormalizeUrl(string url)
|
private static string NormalizeUrl(string url)
|
||||||
{
|
{
|
||||||
// Remove signature parameters from Discord CDN URLs to normalize them
|
// Remove signature parameters from Discord CDN/media URLs to normalize them
|
||||||
var uri = new Uri(url);
|
var uri = new Uri(url);
|
||||||
if (!string.Equals(uri.Host, "cdn.discordapp.com", StringComparison.OrdinalIgnoreCase))
|
|
||||||
|
if (
|
||||||
|
!string.Equals(uri.Host, "cdn.discordapp.com", StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& !string.Equals(uri.Host, "media.discordapp.net", StringComparison.OrdinalIgnoreCase)
|
||||||
|
)
|
||||||
|
{
|
||||||
return url;
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
var query = HttpUtility.ParseQueryString(uri.Query);
|
var query = HttpUtility.ParseQueryString(uri.Query);
|
||||||
query.Remove("ex");
|
query.Remove("ex");
|
||||||
@@ -96,7 +114,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))
|
||||||
@@ -130,13 +148,16 @@ internal partial class ExportAssetDownloader
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Legacy naming used a 5-char hash, kept for backwards compatibility with existing exports
|
// Legacy naming used a 5-char hash, kept for backwards compatibility with existing exports
|
||||||
private static string GetLegacyFileNameFromUrl(string url) =>
|
private static IReadOnlyList<string> GetLegacyFileNamesFromUrl(string url)
|
||||||
GetFileNameFromUrl(
|
{
|
||||||
url,
|
var hashData = SHA256.HashData(Encoding.UTF8.GetBytes(NormalizeUrl(url)));
|
||||||
SHA256
|
|
||||||
.HashData(Encoding.UTF8.GetBytes(NormalizeUrl(url)))
|
return
|
||||||
.Pipe(Convert.ToHexStringLower)
|
[
|
||||||
// 5 chars = 20 bits, reaches 1% collision probability at ~145 files
|
// Lowercase variant (introduced in 2.46.1)
|
||||||
.Truncate(5)
|
GetFileNameFromUrl(url, Convert.ToHexStringLower(hashData).Truncate(5)),
|
||||||
);
|
// Uppercase variant (original)
|
||||||
|
GetFileNameFromUrl(url, Convert.ToHexString(hashData).Truncate(5)),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
@@ -206,10 +206,15 @@ public partial class ExportRequest
|
|||||||
{
|
{
|
||||||
var actualOutputPath = FormatPath(outputPath, guild, channel, after, before);
|
var actualOutputPath = FormatPath(outputPath, guild, channel, after, before);
|
||||||
|
|
||||||
// Output is a directory
|
// Determine whether the output path refers to a directory or a file.
|
||||||
|
// The extension-based heuristic is evaluated on the original, unsubstituted path,
|
||||||
|
// because the value of a template token (e.g. a guild or channel name) may contain
|
||||||
|
// a period that would otherwise be mistaken for a file extension, incorrectly causing
|
||||||
|
// a directory path to be treated as a file.
|
||||||
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1502
|
||||||
if (
|
if (
|
||||||
Directory.Exists(actualOutputPath)
|
Directory.Exists(actualOutputPath)
|
||||||
|| string.IsNullOrWhiteSpace(Path.GetExtension(actualOutputPath))
|
|| string.IsNullOrWhiteSpace(Path.GetExtension(outputPath))
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var fileName = GetDefaultOutputFileName(guild, channel, format, after, before);
|
var fileName = GetDefaultOutputFileName(guild, channel, format, after, before);
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user