Compare commits

..

20 Commits

Author SHA1 Message Date
Oleksii Holub 6f47fb6a1c Update version 2022-06-01 19:55:49 +03:00
Oleksii Holub 6150720b1e Update readme 2022-05-31 12:29:31 +03:00
Oleksii Holub dcdeea19ab Update nuget packages 2022-05-29 22:43:42 +03:00
Oleksii Holub ca56d47110 Allow whitespace in sticker names 2022-05-20 20:45:42 +03:00
Oleksii Holub 17b548d679 Add test for last change 2022-05-18 20:19:46 +03:00
Andrew Kolos 20eeaa58e9 Add has:pin filter (#853)
Adds a new `has:*` filter. Specifically, `has:pin`.
The filter filters for messages that are pinned.
2022-05-18 10:11:50 -07:00
Oleksii Holub 6621c10ae0 Satisfy my OCD 2022-04-30 16:04:57 +03:00
Oleksii Holub 75214354ca Update CliFx 2022-04-22 19:48:11 +03:00
Oleksii Holub 84d99e8520 Reorganize assets 2022-04-22 17:19:51 +03:00
Oleksii Holub 4b80eabb73 Update NuGet.config 2022-04-22 17:03:03 +03:00
Oleksii Holub e6830bbd0e Plural artifacts 2022-04-22 02:10:00 +03:00
Oleksii Holub 97c751f171 Simplify workflows 2022-04-22 01:23:48 +03:00
Oleksii Holub dc814e08e7 Update readme 2022-04-22 01:14:35 +03:00
Oleksii Holub 54bf7c204a Consolidate workflows 2022-04-22 01:13:36 +03:00
Oleksii Holub 36c9983c9e Update test logger 2022-04-19 04:29:50 +03:00
Oleksii Holub 361e0489ed Update NuGet packages 2022-04-17 03:36:15 +03:00
Oleksii Holub 77436120a8 Update readme 2022-04-16 18:48:39 +03:00
Oleksii Holub 2d7ab5411f Revert "Use nightly.link for linking CI builds"
This reverts commit 1ce523b252.
2022-04-13 22:40:27 +03:00
Oleksii Holub 1ce523b252 Use nightly.link for linking CI builds 2022-04-13 22:38:41 +03:00
Oleksii Holub bb81cf06ae Rework retry policy
Fixes #832
2022-04-12 23:55:03 +03:00
26 changed files with 370 additions and 273 deletions

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

-30
View File
@@ -1,30 +0,0 @@
name: Docker CD
on:
push:
tags:
- "*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2.3.3
- name: Get release version
id: get_version
uses: dawidd6/action-get-tag@v1
- name: Login
run: docker login -u tyrrrz -p ${{ secrets.DOCKER_TOKEN }}
- name: Build
run: docker build -t tyrrrz/discordchatexporter:${{ steps.get_version.outputs.tag }} -t tyrrrz/discordchatexporter:stable .
- name: Deploy (version)
run: docker push tyrrrz/discordchatexporter:${{ steps.get_version.outputs.tag }}
- name: Deploy (stable)
run: docker push tyrrrz/discordchatexporter:stable
-85
View File
@@ -1,85 +0,0 @@
name: CD
on:
push:
tags:
- "*"
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2.3.3
- name: Get release version
id: get_version
uses: dawidd6/action-get-tag@v1
- name: Install .NET
uses: actions/setup-dotnet@v1.7.2
with:
# Fixed version, workaround for https://github.com/dotnet/core/issues/7176
dotnet-version: 6.0.100
- name: Build & publish (CLI)
run: dotnet publish DiscordChatExporter.Cli/ -o DiscordChatExporter.Cli/bin/Publish/ --configuration Release
- name: Build & publish (GUI)
run: dotnet publish DiscordChatExporter.Gui/ -o DiscordChatExporter.Gui/bin/Publish/ --configuration Release
- name: Pack (CLI)
run: Compress-Archive -Path DiscordChatExporter.Cli/bin/Publish/* -DestinationPath DiscordChatExporter.Cli/bin/Publish/Archive.zip -Force
shell: pwsh
- name: Pack (GUI)
run: Compress-Archive -Path DiscordChatExporter.Gui/bin/Publish/* -DestinationPath DiscordChatExporter.Gui/bin/Publish/Archive.zip -Force
shell: pwsh
- name: Create release
id: create_release
uses: actions/create-release@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: |
[Changelog](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/Changelog.md)
draft: false
prerelease: false
- name: Upload release asset (CLI)
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: DiscordChatExporter.Cli/bin/Publish/Archive.zip
asset_name: DiscordChatExporter.CLI.zip
asset_content_type: application/zip
- name: Upload release asset (GUI)
uses: actions/upload-release-asset@v1.0.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: DiscordChatExporter.Gui/bin/Publish/Archive.zip
asset_name: DiscordChatExporter.zip
asset_content_type: application/zip
- name: Notify Discord
uses: satak/webrequest-action@v1.2.4
with:
url: ${{ secrets.DISCORD_WEBHOOK }}
method: POST
headers: |
{
"ContentType": "application/json; charset=UTF-8"
}
payload: |
{
"content": "**DiscordChatExporter** new version released!\nVersion: `${{ steps.get_version.outputs.tag }}`\nChangelog: <https://github.com/Tyrrrz/DiscordChatExporter/blob/${{ steps.get_version.outputs.tag }}/Changelog.md>"
}
-23
View File
@@ -1,23 +0,0 @@
name: Docker CI
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2.3.3
- name: Login
run: docker login -u tyrrrz -p ${{ secrets.DOCKER_TOKEN }}
- name: Build
run: docker build -t tyrrrz/discordchatexporter:latest .
- name: Deploy (latest)
run: docker push tyrrrz/discordchatexporter:latest
-48
View File
@@ -1,48 +0,0 @@
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2.3.3
- name: Install .NET
uses: actions/setup-dotnet@v1.7.2
with:
# Fixed version, workaround for https://github.com/dotnet/core/issues/7176
dotnet-version: 6.0.100
- name: Build & test
# Tests need access to secrets, so we can't run them against PRs because of limited trust
if: ${{ github.event_name != 'pull_request' }}
run: dotnet test --configuration Release --logger GitHubActions --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_TOKEN_BOT: true
- name: Upload coverage
uses: codecov/codecov-action@v1.0.5
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Build & publish (CLI)
run: dotnet publish DiscordChatExporter.Cli/ -o DiscordChatExporter.Cli/bin/Publish/ --configuration Release
- name: Build & publish (GUI)
run: dotnet publish DiscordChatExporter.Gui/ -o DiscordChatExporter.Gui/bin/Publish/ --configuration Release
- name: Upload build artifacts (CLI)
uses: actions/upload-artifact@v1
with:
name: DiscordChatExporter.CLI
path: DiscordChatExporter.Cli/bin/Publish/
- name: Upload build artifact (GUI)
uses: actions/upload-artifact@v1
with:
name: DiscordChatExporter
path: DiscordChatExporter.Gui/bin/Publish/
+55
View File
@@ -0,0 +1,55 @@
name: docker
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Build container
run: docker build .
deploy-latest:
if: ${{ github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'master' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to DockerHub
run: docker login -u tyrrrz -p ${{ secrets.DOCKER_TOKEN }}
- name: Build container
run: docker build -t tyrrrz/discordchatexporter:latest .
- name: Push container
run: docker push tyrrrz/discordchatexporter:latest
deploy-stable:
if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Get release version
id: get_version
uses: dawidd6/action-get-tag@v1
- name: Login to DockerHub
run: docker login -u tyrrrz -p ${{ secrets.DOCKER_TOKEN }}
- name: Build container
run: docker build -t tyrrrz/discordchatexporter:stable .
- name: Push container (stable)
run: docker push tyrrrz/discordchatexporter:stable
- name: Push container (${{ steps.get_version.outputs.tag }})
run: docker push tyrrrz/discordchatexporter:${{ steps.get_version.outputs.tag }}
+154
View File
@@ -0,0 +1,154 @@
name: main
on: [push, pull_request]
jobs:
test:
runs-on: windows-latest
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install .NET
uses: actions/setup-dotnet@v2
with:
# Fixed version, workaround for https://github.com/dotnet/core/issues/7176
dotnet-version: 6.0.100
- name: Run tests
# Tests need access to secrets, so we can't run them against PRs because of limited trust
if: ${{ github.event_name != 'pull_request' }}
run: dotnet test --configuration Release --logger GitHubActions --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
env:
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DISCORD_TOKEN_BOT: true
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
publish:
needs: test
runs-on: windows-latest
env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install .NET
uses: actions/setup-dotnet@v2
with:
# Fixed version, workaround for https://github.com/dotnet/core/issues/7176
dotnet-version: 6.0.100
- name: Publish (CLI)
run: dotnet publish DiscordChatExporter.Cli/ -o DiscordChatExporter.Cli/bin/Publish/ --configuration Release
- name: Publish (GUI)
run: dotnet publish DiscordChatExporter.Gui/ -o DiscordChatExporter.Gui/bin/Publish/ --configuration Release
- name: Upload artifacts (CLI)
uses: actions/upload-artifact@v3
with:
name: DiscordChatExporter.Cli
path: DiscordChatExporter.Cli/bin/Publish/
- name: Upload artifacts (GUI)
uses: actions/upload-artifact@v3
with:
name: DiscordChatExporter
path: DiscordChatExporter.Gui/bin/Publish/
deploy:
if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
needs: publish
runs-on: ubuntu-latest
steps:
- name: Download artifacts (CLI)
uses: actions/download-artifact@v3
with:
name: DiscordChatExporter.Cli
path: DiscordChatExporter.Cli
- name: Download artifacts (GUI)
uses: actions/download-artifact@v3
with:
name: DiscordChatExporter
path: DiscordChatExporter.Gui
- name: Create package (CLI)
run: Compress-Archive -Path DiscordChatExporter.Cli/* -DestinationPath DiscordChatExporter.Cli.zip -Force
shell: pwsh
- name: Create package (GUI)
run: Compress-Archive -Path DiscordChatExporter.Gui/* -DestinationPath DiscordChatExporter.Gui.zip -Force
shell: pwsh
- name: Create release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
body: |
[Changelog](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/Changelog.md)
draft: false
prerelease: false
- name: Upload release asset (CLI)
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_name: DiscordChatExporter.Cli.zip
asset_path: DiscordChatExporter.Cli.zip
asset_content_type: application/zip
- name: Upload release asset (GUI)
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_name: DiscordChatExporter.zip
asset_path: DiscordChatExporter.Gui.zip
asset_content_type: application/zip
notify:
needs: deploy
runs-on: ubuntu-latest
steps:
- name: Get release version
id: get_version
uses: dawidd6/action-get-tag@v1
- name: Notify Discord
uses: satak/webrequest-action@v1.2.4
with:
url: ${{ secrets.DISCORD_WEBHOOK }}
method: POST
headers: |
{
"ContentType": "application/json; charset=UTF-8"
}
payload: |
{
"content": "**DiscordChatExporter** new version released!\nVersion: `${{ steps.get_version.outputs.tag }}`\nChangelog: <https://github.com/Tyrrrz/DiscordChatExporter/blob/${{ steps.get_version.outputs.tag }}/Changelog.md>"
}
+6
View File
@@ -1,3 +1,9 @@
### v2.34.1 (01-Jun-2022)
- Improved retry policy to handle more transient errors. This should result in fewer errors exporting a channel due to timeout.
- Added `has:pin` message filter. You can use it to limit exported messages to those that have been pinned. (Thanks [@Andrew Kolos](https://github.com/andrewkolos))
- Fixed an issue which caused the export to fail when processing stickers that have no name.
### v2.34 (10-Apr-2022)
- [HTML] Reworked layout to be more consistent with Discord.
+1 -1
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>2.34</Version>
<Version>2.34.1</Version>
<Company>Tyrrrz</Company>
<Copyright>Copyright (c) Oleksii Holub</Copyright>
<LangVersion>preview</LangVersion>
@@ -13,13 +13,13 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.16.1" />
<PackageReference Include="FluentAssertions" Version="6.6.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="1.3.0" PrivateAssets="all" />
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="GitHubActionsTestLogger" Version="2.0.0" PrivateAssets="all" />
<PackageReference Include="JsonExtensions" Version="1.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" PrivateAssets="all" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" PrivateAssets="all" />
<PackageReference Include="coverlet.collector" Version="3.1.2" PrivateAssets="all" />
</ItemGroup>
+2 -3
View File
@@ -3,12 +3,11 @@
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.
1. Join the server: https://discord.gg/eRV8Vap5bm
1. [Join the test server](https://discord.gg/eRV8Vap5bm)
2. Locate your Discord authentication token
3. Specify your token using a file or an environment variable:
- **Using a file**: put your token in a new `DiscordToken.secret` file created in this directory
- **Using an environment variable**: set `DISCORD_TOKEN` variable to your token
4. Run the tests: `dotnet test`
> If you want to have a new test case or a scenario added, please let me know in your pull request.
Currently, it's not possible to add them by yourself.
> If you want to add a new test case, please let me know and I will give you the required permissions
@@ -100,6 +100,34 @@ public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutp
.ContainSingle("This has image");
}
[Fact]
public async Task Messages_filtered_by_pin_only_include_messages_that_have_been_pinned()
{
// Arrange
var filePath = TempOutput.GetTempFilePath();
// Act
await new ExportChannelsCommand
{
Token = Secrets.DiscordToken,
ChannelIds = new[] { ChannelIds.FilterTestCases },
ExportFormat = ExportFormat.Json,
OutputPath = filePath,
MessageFilter = MessageFilter.Parse("has:pin")
}.ExecuteAsync(new FakeConsole());
var data = await File.ReadAllTextAsync(filePath);
var document = Json.Parse(data);
// Assert
document
.GetProperty("messages")
.EnumerateArray()
.Select(j => j.GetProperty("content").GetString())
.Should()
.ContainSingle("This is pinned");
}
[Fact]
public async Task Messages_filtered_by_mention_only_include_messages_that_have_that_mention()
{
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CliFx" Version="2.2.2" />
<PackageReference Include="CliFx" Version="2.2.5" />
<PackageReference Include="Spectre.Console" Version="0.44.0" />
<PackageReference Include="Gress" Version="2.0.1" />
</ItemGroup>
@@ -4,6 +4,7 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data;
// https://discord.com/developers/docs/resources/sticker#sticker-resource
public record Sticker(Snowflake Id, string Name, StickerFormat Format, string SourceUrl)
{
private static string GetSourceUrl(Snowflake id, StickerFormat format)
@@ -15,7 +16,7 @@ public record Sticker(Snowflake Id, string Name, StickerFormat Format, string So
public static Sticker Parse(JsonElement json)
{
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var name = json.GetProperty("name").GetNonWhiteSpaceString();
var name = json.GetProperty("name").GetNonNullString();
var format = (StickerFormat)json.GetProperty("format_type").GetInt32();
var sourceUrl = GetSourceUrl(id, format);
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
@@ -52,7 +51,7 @@ public class DiscordClient
string url,
CancellationToken cancellationToken = default)
{
return await Http.ResponsePolicy.ExecuteAsync(async innerCancellationToken =>
return await Http.ResponseResiliencePolicy.ExecuteAsync(async innerCancellationToken =>
{
if (_tokenKind == TokenKind.User)
return await GetResponseAsync(url, false, innerCancellationToken);
@@ -19,6 +19,7 @@ internal class HasMessageFilter : MessageFilter
MessageContentMatchKind.Video => message.Attachments.Any(file => file.IsVideo),
MessageContentMatchKind.Image => message.Attachments.Any(file => file.IsImage),
MessageContentMatchKind.Sound => message.Attachments.Any(file => file.IsAudio),
MessageContentMatchKind.Pin => message.IsPinned,
_ => throw new InvalidOperationException($"Unknown message content match kind '{_kind}'.")
};
}
@@ -7,5 +7,6 @@ internal enum MessageContentMatchKind
File,
Video,
Image,
Sound
Sound,
Pin
}
@@ -58,7 +58,8 @@ internal static class FilterGrammar
Span.EqualToIgnoreCase("file").IgnoreThen(Parse.Return(MessageContentMatchKind.File)),
Span.EqualToIgnoreCase("video").IgnoreThen(Parse.Return(MessageContentMatchKind.Video)),
Span.EqualToIgnoreCase("image").IgnoreThen(Parse.Return(MessageContentMatchKind.Image)),
Span.EqualToIgnoreCase("sound").IgnoreThen(Parse.Return(MessageContentMatchKind.Sound))
Span.EqualToIgnoreCase("sound").IgnoreThen(Parse.Return(MessageContentMatchKind.Sound)),
Span.EqualToIgnoreCase("pin").IgnoreThen(Parse.Return(MessageContentMatchKind.Pin))
))
.Select(k => (MessageFilter) new HasMessageFilter(k))
.Named("has:<value>");
@@ -35,45 +35,42 @@ internal partial class MediaDownloader
var filePath = Path.Combine(_workingDirPath, fileName);
// Reuse existing files if we're allowed to
if (_reuseMedia && File.Exists(filePath))
return _pathCache[url] = filePath;
Directory.CreateDirectory(_workingDirPath);
// This retries on IOExceptions which is dangerous as we're also working with files
await Http.ExceptionPolicy.ExecuteAsync(async () =>
if (!_reuseMedia || !File.Exists(filePath))
{
// Download the file
using var response = await Http.Client.GetAsync(url, cancellationToken);
await using (var output = File.Create(filePath))
{
await response.Content.CopyToAsync(output, cancellationToken);
}
Directory.CreateDirectory(_workingDirPath);
// Try to set the file date according to the last-modified header
try
await Http.ResiliencePolicy.ExecuteAsync(async () =>
{
var lastModified = response.Content.Headers.TryGetValue("Last-Modified")?.Pipe(s =>
DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)
? date
: (DateTimeOffset?) null
);
// Download the file
using var response = await Http.Client.GetAsync(url, cancellationToken);
await using (var output = File.Create(filePath))
await response.Content.CopyToAsync(output, cancellationToken);
if (lastModified is not null)
// Try to set the file date according to the last-modified header
try
{
File.SetCreationTimeUtc(filePath, lastModified.Value.UtcDateTime);
File.SetLastWriteTimeUtc(filePath, lastModified.Value.UtcDateTime);
File.SetLastAccessTimeUtc(filePath, lastModified.Value.UtcDateTime);
var lastModified = response.Content.Headers.TryGetValue("Last-Modified")?.Pipe(s =>
DateTimeOffset.TryParse(s, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)
? date
: (DateTimeOffset?) null
);
if (lastModified is not null)
{
File.SetCreationTimeUtc(filePath, lastModified.Value.UtcDateTime);
File.SetLastWriteTimeUtc(filePath, lastModified.Value.UtcDateTime);
File.SetLastAccessTimeUtc(filePath, lastModified.Value.UtcDateTime);
}
}
}
catch
{
// This can apparently fail for some reason.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/585
// Updating file dates is not a critical task, so we'll just
// ignore exceptions thrown here.
}
});
catch
{
// This can apparently fail for some reason.
// https://github.com/Tyrrrz/DiscordChatExporter/issues/585
// Updating file dates is not a critical task, so we'll just
// ignore exceptions thrown here.
}
});
}
return _pathCache[url] = filePath;
}
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Utils.Extensions;
public static class ExceptionExtensions
{
private static void PopulateChildren(this Exception exception, ICollection<Exception> children)
{
if (exception is AggregateException aggregateException)
{
foreach (var innerException in aggregateException.InnerExceptions)
{
children.Add(innerException);
PopulateChildren(innerException, children);
}
}
else if (exception.InnerException is not null)
{
children.Add(exception.InnerException);
PopulateChildren(exception.InnerException, children);
}
}
public static IReadOnlyList<Exception> GetSelfAndChildren(this Exception exception)
{
var children = new List<Exception> {exception};
PopulateChildren(exception, children);
return children;
}
public static HttpStatusCode? TryGetStatusCode(this HttpRequestException ex) =>
// This is extremely frail, but there's no other way
Regex
.Match(ex.Message, @": (\d+) \(")
.Groups[1]
.Value
.NullIfWhiteSpace()?
.Pipe(s => (HttpStatusCode) int.Parse(s, CultureInfo.InvariantCulture));
}
+21 -29
View File
@@ -1,9 +1,8 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Utils.Extensions;
using Polly;
@@ -14,13 +13,26 @@ public static class Http
{
public static HttpClient Client { get; } = new();
public static IAsyncPolicy<HttpResponseMessage> ResponsePolicy { get; } =
private static bool IsRetryableStatusCode(HttpStatusCode statusCode) => statusCode is
HttpStatusCode.TooManyRequests or
HttpStatusCode.RequestTimeout or
HttpStatusCode.InternalServerError;
private static bool IsRetryableException(Exception exception) =>
exception.GetSelfAndChildren().Any(ex =>
ex is TimeoutException or SocketException ||
ex is HttpRequestException hrex && IsRetryableStatusCode(hrex.TryGetStatusCode() ?? HttpStatusCode.OK)
);
public static IAsyncPolicy ResiliencePolicy { get; } =
Policy
.Handle<IOException>()
.Or<HttpRequestException>()
.OrResult<HttpResponseMessage>(m => m.StatusCode == HttpStatusCode.TooManyRequests)
.OrResult(m => m.StatusCode == HttpStatusCode.RequestTimeout)
.OrResult(m => m.StatusCode >= HttpStatusCode.InternalServerError)
.Handle<Exception>(IsRetryableException)
.WaitAndRetryAsync(4, i => TimeSpan.FromSeconds(Math.Pow(2, i) + 1));
public static IAsyncPolicy<HttpResponseMessage> ResponseResiliencePolicy { get; } =
Policy
.Handle<Exception>(IsRetryableException)
.OrResult<HttpResponseMessage>(m => IsRetryableStatusCode(m.StatusCode))
.WaitAndRetryAsync(
8,
(i, result, _) =>
@@ -43,24 +55,4 @@ public static class Http
},
(_, _, _, _) => Task.CompletedTask
);
private static HttpStatusCode? TryGetStatusCodeFromException(HttpRequestException ex) =>
// This is extremely frail, but there's no other way
Regex
.Match(ex.Message, @": (\d+) \(")
.Groups[1]
.Value
.NullIfWhiteSpace()?
.Pipe(s => (HttpStatusCode) int.Parse(s, CultureInfo.InvariantCulture));
public static IAsyncPolicy ExceptionPolicy { get; } =
Policy
.Handle<IOException>() // dangerous
.Or<HttpRequestException>(ex =>
TryGetStatusCodeFromException(ex) is
HttpStatusCode.TooManyRequests or
HttpStatusCode.RequestTimeout or
HttpStatusCode.InternalServerError
)
.WaitAndRetryAsync(4, i => TimeSpan.FromSeconds(Math.Pow(2, i) + 1));
}
@@ -14,14 +14,14 @@
<ItemGroup>
<PackageReference Include="Gress" Version="2.0.1" />
<PackageReference Include="MaterialDesignColors" Version="2.0.5" />
<PackageReference Include="MaterialDesignThemes" Version="4.4.0" />
<PackageReference Include="MaterialDesignColors" Version="2.0.6" />
<PackageReference Include="MaterialDesignThemes" Version="4.5.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="Onova" Version="2.6.2" />
<PackageReference Include="Stylet" Version="1.3.6" />
<PackageReference Include="Tyrrrz.Settings" Version="1.3.4" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" PrivateAssets="all" />
<PackageReference Include="PropertyChanged.Fody" Version="3.4.1" PrivateAssets="all" />
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.2.0" PrivateAssets="all" />
</ItemGroup>
+5 -2
View File
@@ -2,6 +2,9 @@
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
<config>
<add key="defaultPushSource" value="https://api.nuget.org/v3/index.json" />
</config>
</configuration>
+8 -6
View File
@@ -1,7 +1,7 @@
# DiscordChatExporter
[![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://vshymanskyy.github.io/StandWithUkraine)
[![Build](https://img.shields.io/github/workflow/status/Tyrrrz/DiscordChatExporter/CI/master)](https://github.com/Tyrrrz/DiscordChatExporter/actions)
[![Build](https://img.shields.io/github/workflow/status/Tyrrrz/DiscordChatExporter/main/master)](https://github.com/Tyrrrz/DiscordChatExporter/actions)
[![Coverage](https://img.shields.io/codecov/c/github/Tyrrrz/DiscordChatExporter/master)](https://codecov.io/gh/Tyrrrz/DiscordChatExporter)
[![Release](https://img.shields.io/github/release/Tyrrrz/DiscordChatExporter.svg)](https://github.com/Tyrrrz/DiscordChatExporter/releases)
[![Downloads](https://img.shields.io/github/downloads/Tyrrrz/DiscordChatExporter/total.svg)](https://github.com/Tyrrrz/DiscordChatExporter/releases)
@@ -16,7 +16,9 @@ It works with direct messages, group messages, and server channels, and supports
> ❔ If you have questions or issues, **please refer to the [wiki](https://github.com/Tyrrrz/DiscordChatExporter/wiki)**.
## Terms of use
> 💬 If you want to chat, **join my [Discord server](https://discord.gg/2SUWKFnHSm)**.
## Terms of use<sup>[[?]](https://github.com/Tyrrrz/.github/blob/master/docs/why-so-political.md)</sup>
By using this project or its source code, for any purpose and in any shape or form, you grant your **implicit agreement** to all the following statements:
@@ -46,7 +48,7 @@ The following table lists all available download options:
<td>
<ul>
<li>🟢 <b><a href="https://github.com/Tyrrrz/DiscordChatExporter/releases/latest">Stable release</a></b> (<code>DiscordChatExporter.zip</code>)</li>
<li>🟠 <a href="https://github.com/Tyrrrz/DiscordChatExporter/actions/workflows/CI.yml">CI build</a> (<code>DiscordChatExporter.zip</code>)</li>
<li>🟠 <a href="https://github.com/Tyrrrz/DiscordChatExporter/actions/workflows/main.yml">CI build</a> (<code>DiscordChatExporter.zip</code>)</li>
</ul>
</td>
<td>
@@ -60,7 +62,7 @@ The following table lists all available download options:
<td>
<ul>
<li>🟢 <b><a href="https://github.com/Tyrrrz/DiscordChatExporter/releases/latest">Stable release</a></b> (<code>DiscordChatExporter.CLI.zip</code>) ⚙️</li>
<li>🟠 <a href="https://github.com/Tyrrrz/DiscordChatExporter/actions/workflows/CI.yml">CI build</a> (<code>DiscordChatExporter.CLI.zip</code>) ⚙️</li>
<li>🟠 <a href="https://github.com/Tyrrrz/DiscordChatExporter/actions/workflows/main.yml">CI build</a> (<code>DiscordChatExporter.CLI.zip</code>) ⚙️</li>
<li>🐋 <a href="https://hub.docker.com/r/tyrrrz/discordchatexporter">Docker</a> (<code>tyrrrz/discordchatexporter</code>)</li>
<li>📦 <a href="https://aur.archlinux.org/packages/discord-chat-exporter-cli">AUR</a> (<code>discord-chat-exporter-cli</code>) 🦄</li>
</ul>
@@ -95,5 +97,5 @@ The following table lists all available download options:
## Screenshots
![channel list](.screenshots/list.png)
![rendered output](.screenshots/output.png)
![channel list](.assets/list.png)
![rendered output](.assets/output.png)
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB