mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-08 23:24:39 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f8749a105 | |||
| 4896d748aa | |||
| 4876ed192a | |||
| 4f6e6f4f0b | |||
| 8c75f04115 | |||
| df04b123cd | |||
| 578ed9be17 | |||
| ebceacc707 | |||
| eaab5786f2 | |||
| ebf7d65334 |
@@ -1,6 +1,8 @@
|
||||
name: docker
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
# Outputs from this job aren't really used, but it's here to verify that
|
||||
@@ -31,6 +33,7 @@ jobs:
|
||||
with:
|
||||
name: DiscordChatExporter.Cli.Docker
|
||||
path: DiscordChatExporter.Cli.Docker.tar
|
||||
if-no-files-found: error
|
||||
|
||||
deploy:
|
||||
# Deploy to DockerHub only on tag push or master branch push
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
name: main
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
@@ -73,6 +75,7 @@ jobs:
|
||||
with:
|
||||
name: ${{ matrix.app }}
|
||||
path: ${{ matrix.app }}/bin/publish/
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
|
||||
@@ -90,13 +93,15 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: >
|
||||
gh release create "${{ github.ref_name }}"
|
||||
--repo "${{ github.event.repository.full_name }}"
|
||||
--title "${{ github.ref_name }}"
|
||||
gh release create ${{ github.ref_name }}
|
||||
--repo ${{ github.event.repository.full_name }}
|
||||
--title ${{ github.ref_name }}
|
||||
--notes "[Changelog](${{ github.event.repository.html_url }}/blob/${{ github.ref_name }}/Changelog.md)"
|
||||
--verify-tag
|
||||
|
||||
deploy:
|
||||
needs: release
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
app:
|
||||
@@ -109,7 +114,6 @@ jobs:
|
||||
# GUI asset isn't suffixed, unlike the CLI asset
|
||||
asset: DiscordChatExporter
|
||||
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
@@ -127,13 +131,13 @@ jobs:
|
||||
working-directory: ${{ matrix.app }}/
|
||||
run: zip -r ../${{ matrix.asset }}.zip .
|
||||
|
||||
- name: Create release
|
||||
- name: Upload release asset
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: >
|
||||
gh release upload "${{ github.ref_name }}"
|
||||
"${{ matrix.asset }}.zip"
|
||||
--repo "${{ github.event.repository.full_name }}"
|
||||
gh release upload ${{ github.ref_name }}
|
||||
${{ matrix.asset }}.zip
|
||||
--repo ${{ github.event.repository.full_name }}
|
||||
|
||||
notify:
|
||||
needs: deploy
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## v2.42.3 (09-Nov-2023)
|
||||
|
||||
- JSON changes:
|
||||
- Added the `video` field to the embed object, which may contain additional information about an embedded video resource.
|
||||
- CSV changes:
|
||||
- Fixed an issue where the `Date` field was incorrectly formatted. Now, the dates follow the `ISO 8601` standard.
|
||||
|
||||
## v2.42.2 (26-Oct-2023)
|
||||
|
||||
- General changes:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<Version>2.42.2</Version>
|
||||
<Version>2.42.3</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<Copyright>Copyright (c) Oleksii Holub</Copyright>
|
||||
<LangVersion>preview</LangVersion>
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="1.0.5" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="ReflectionMagic" Version="5.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit" Version="2.6.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-preview AS build
|
||||
# we can build the assembly for the correct platform.
|
||||
ARG TARGETARCH
|
||||
|
||||
WORKDIR /build
|
||||
WORKDIR /tmp/dce
|
||||
|
||||
COPY favicon.ico .
|
||||
COPY NuGet.config .
|
||||
@@ -22,17 +22,24 @@ COPY DiscordChatExporter.Cli DiscordChatExporter.Cli
|
||||
|
||||
# Publish a self-contained assembly so we can use a slimmer runtime image
|
||||
RUN dotnet publish DiscordChatExporter.Cli \
|
||||
--configuration Release \
|
||||
-p:CSharpier_Bypass=true \
|
||||
--configuration Release \
|
||||
--self-contained \
|
||||
--use-current-runtime \
|
||||
--arch $TARGETARCH \
|
||||
--output publish/
|
||||
--output DiscordChatExporter.Cli/bin/publish/
|
||||
|
||||
# -- Run
|
||||
# Use `runtime-deps` instead of `runtime` because we have a self-contained assembly
|
||||
FROM --platform=$TARGETPLATFORM mcr.microsoft.com/dotnet/runtime-deps:7.0 AS run
|
||||
|
||||
LABEL org.opencontainers.image.title="DiscordChatExporter.Cli"
|
||||
LABEL org.opencontainers.image.description="DiscordChatExporter is an application that can be used to export message history from any Discord channel to a file."
|
||||
LABEL org.opencontainers.image.authors="tyrrrz.me"
|
||||
LABEL org.opencontainers.image.url="https://github.com/Tyrrrz/DiscordChatExporter"
|
||||
LABEL org.opencontainers.image.source="https://github.com/Tyrrrz/DiscordChatExporter/blob/master/DiscordChatExporter.Cli.dockerfile"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
|
||||
# Create a non-root user to run the app, so that the output files can be accessed by the host
|
||||
# https://github.com/Tyrrrz/DiscordChatExporter/issues/851
|
||||
RUN adduser --disabled-password --no-create-home dce
|
||||
@@ -42,5 +49,5 @@ USER dce
|
||||
# stays the same for backwards compatibility.
|
||||
WORKDIR /out
|
||||
|
||||
COPY --from=build /build/publish /opt/dce
|
||||
COPY --from=build /tmp/dce/DiscordChatExporter.Cli/bin/publish /opt/dce
|
||||
ENTRYPOINT ["/opt/dce/DiscordChatExporter.Cli"]
|
||||
@@ -37,11 +37,13 @@ public abstract class DiscordCommandBase : ICommand
|
||||
{
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkYellow))
|
||||
{
|
||||
console.Error.WriteLine(
|
||||
"Warning: Option --bot is deprecated and should not be used. "
|
||||
+ "The type of the provided token is now inferred automatically. "
|
||||
+ "Please update your workflows as this option may be completely removed in a future version."
|
||||
);
|
||||
console
|
||||
.Error
|
||||
.WriteLine(
|
||||
"Warning: Option --bot is deprecated and should not be used. "
|
||||
+ "The type of the provided token is now inferred automatically. "
|
||||
+ "Please update your workflows as this option may be completely removed in a future version."
|
||||
);
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
@@ -244,9 +244,11 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
// Print the result
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
{
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Successfully exported {channels.Count - errorsByChannel.Count} channel(s)."
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteLineAsync(
|
||||
$"Successfully exported {channels.Count - errorsByChannel.Count} channel(s)."
|
||||
);
|
||||
}
|
||||
|
||||
// Print errors
|
||||
@@ -256,9 +258,11 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
|
||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||
{
|
||||
await console.Error.WriteLineAsync(
|
||||
$"Failed to export {errorsByChannel.Count} the following channel(s):"
|
||||
);
|
||||
await console
|
||||
.Error
|
||||
.WriteLineAsync(
|
||||
$"Failed to export {errorsByChannel.Count} the following channel(s):"
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var (channel, error) in errorsByChannel)
|
||||
@@ -320,33 +324,51 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
// Support Ukraine callout
|
||||
if (!IsUkraineSupportMessageDisabled)
|
||||
{
|
||||
console.Output.WriteLine(
|
||||
"┌────────────────────────────────────────────────────────────────────┐"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ Thank you for supporting Ukraine <3 │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ As Russia wages a genocidal war against my country, │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ I'm grateful to everyone who continues to │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ stand with Ukraine in our fight for freedom. │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"│ Learn more: https://tyrrrz.me/ukraine │"
|
||||
);
|
||||
console.Output.WriteLine(
|
||||
"└────────────────────────────────────────────────────────────────────┘"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"┌────────────────────────────────────────────────────────────────────┐"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ Thank you for supporting Ukraine <3 │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ As Russia wages a genocidal war against my country, │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ I'm grateful to everyone who continues to │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ stand with Ukraine in our fight for freedom. │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"│ Learn more: https://tyrrrz.me/ukraine │"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
"└────────────────────────────────────────────────────────────────────┘"
|
||||
);
|
||||
console.Output.WriteLine("");
|
||||
}
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
||||
{
|
||||
// Regular channels
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Fetching channels for guild '{guild.Name}'..."
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteLineAsync($"Fetching channels for guild '{guild.Name}'...");
|
||||
|
||||
var fetchedChannelsCount = 0;
|
||||
await console
|
||||
@@ -94,9 +94,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
// Threads
|
||||
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
||||
{
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Fetching threads for guild '{guild.Name}'..."
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteLineAsync($"Fetching threads for guild '{guild.Name}'...");
|
||||
|
||||
var fetchedThreadsCount = 0;
|
||||
await console
|
||||
@@ -126,9 +126,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
}
|
||||
);
|
||||
|
||||
await console.Output.WriteLineAsync(
|
||||
$"Fetched {fetchedThreadsCount} thread(s)."
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteLineAsync($"Fetched {fetchedThreadsCount} thread(s).");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,9 +180,9 @@ public class ExportAllCommand : ExportCommandBase
|
||||
|
||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||
{
|
||||
await console.Error.WriteLineAsync(
|
||||
"Failed to access the following channel(s):"
|
||||
);
|
||||
await console
|
||||
.Error
|
||||
.WriteLineAsync("Failed to access the following channel(s):");
|
||||
}
|
||||
|
||||
foreach (var dumpChannel in inaccessibleChannels)
|
||||
|
||||
@@ -64,9 +64,9 @@ public class GetChannelsCommand : DiscordCommandBase
|
||||
foreach (var channel in channels)
|
||||
{
|
||||
// Channel ID
|
||||
await console.Output.WriteAsync(
|
||||
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteAsync(channel.Id.ToString().PadRight(channelIdMaxLength, ' '));
|
||||
|
||||
// Separator
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||
@@ -88,9 +88,11 @@ public class GetChannelsCommand : DiscordCommandBase
|
||||
await console.Output.WriteAsync(" * ");
|
||||
|
||||
// Thread ID
|
||||
await console.Output.WriteAsync(
|
||||
channelThread.Id.ToString().PadRight(channelThreadIdMaxLength, ' ')
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteAsync(
|
||||
channelThread.Id.ToString().PadRight(channelThreadIdMaxLength, ' ')
|
||||
);
|
||||
|
||||
// Separator
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||
@@ -106,9 +108,9 @@ public class GetChannelsCommand : DiscordCommandBase
|
||||
|
||||
// Thread status
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
await console.Output.WriteLineAsync(
|
||||
channelThread.IsArchived ? "Archived" : "Active"
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteLineAsync(channelThread.IsArchived ? "Archived" : "Active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,9 @@ public class GetDirectChannelsCommand : DiscordCommandBase
|
||||
foreach (var channel in channels)
|
||||
{
|
||||
// Channel ID
|
||||
await console.Output.WriteAsync(
|
||||
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
||||
);
|
||||
await console
|
||||
.Output
|
||||
.WriteAsync(channel.Id.ToString().PadRight(channelIdMaxLength, ' '));
|
||||
|
||||
// Separator
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||
|
||||
@@ -15,18 +15,20 @@ public class GuideCommand : ICommand
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
console.Output.WriteLine("To get user token:");
|
||||
|
||||
console.Output.WriteLine(
|
||||
" * Automating user accounts is technically against TOS — USE AT YOUR OWN RISK!"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
" * Automating user accounts is technically against TOS — USE AT YOUR OWN RISK!"
|
||||
);
|
||||
console.Output.WriteLine(" 1. Open Discord in your web browser and login");
|
||||
console.Output.WriteLine(" 2. Open any server or direct message channel");
|
||||
console.Output.WriteLine(" 3. Press Ctrl+Shift+I to show developer tools");
|
||||
console.Output.WriteLine(" 4. Navigate to the Network tab");
|
||||
console.Output.WriteLine(" 5. Press Ctrl+R to reload");
|
||||
console.Output.WriteLine(" 6. Switch between random channels to trigger network requests");
|
||||
console.Output.WriteLine(
|
||||
" 7. Search for a request containing \"messages?limit=50\" or similar"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(" 7. Search for a request containing \"messages?limit=50\" or similar");
|
||||
console.Output.WriteLine(" 8. Select the Headers tab on the right");
|
||||
console.Output.WriteLine(" 9. Scroll down to the Request Headers section");
|
||||
console.Output.WriteLine(" 10. Copy the value of the \"authorization\" header");
|
||||
@@ -40,9 +42,11 @@ public class GuideCommand : ICommand
|
||||
console.Output.WriteLine(" 2. Open your application's settings");
|
||||
console.Output.WriteLine(" 3. Navigate to the Bot section on the left");
|
||||
console.Output.WriteLine(" 4. Under Token click Copy");
|
||||
console.Output.WriteLine(
|
||||
" * Your bot needs to have Message Content Intent enabled to read messages"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
" * Your bot needs to have Message Content Intent enabled to read messages"
|
||||
);
|
||||
console.Output.WriteLine();
|
||||
|
||||
// Guild or channel ID
|
||||
@@ -53,20 +57,22 @@ public class GuideCommand : ICommand
|
||||
console.Output.WriteLine(" 2. Open Settings");
|
||||
console.Output.WriteLine(" 3. Go to Advanced section");
|
||||
console.Output.WriteLine(" 4. Enable Developer Mode");
|
||||
console.Output.WriteLine(
|
||||
" 5. Right-click on the desired guild or channel and click Copy Server ID or Copy Channel ID"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine(
|
||||
" 5. Right-click on the desired guild or channel and click Copy Server ID or Copy Channel ID"
|
||||
);
|
||||
console.Output.WriteLine();
|
||||
|
||||
// Docs link
|
||||
using (console.WithForegroundColor(ConsoleColor.White))
|
||||
console.Output.WriteLine(
|
||||
"If you have questions or issues, please refer to the documentation:"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine("If you have questions or issues, please refer to the documentation:");
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
||||
console.Output.WriteLine(
|
||||
"https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs"
|
||||
);
|
||||
console
|
||||
.Output
|
||||
.WriteLine("https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs");
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliFx" Version="2.3.4" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using JsonExtensions.Reading;
|
||||
using System.Text.Json;
|
||||
using JsonExtensions.Reading;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||
|
||||
|
||||
@@ -12,9 +12,11 @@ public partial record SpotifyTrackEmbedProjection
|
||||
private static string? TryParseTrackId(string embedUrl)
|
||||
{
|
||||
// https://open.spotify.com/track/1LHZMWefF9502NPfArRfvP?si=3efac6ce9be04f0a
|
||||
var trackId = Regex.Match(embedUrl, @"spotify\.com/track/(.*?)(?:\?|&|/|$)").Groups[
|
||||
1
|
||||
].Value;
|
||||
var trackId = Regex
|
||||
.Match(embedUrl, @"spotify\.com/track/(.*?)(?:\?|&|/|$)")
|
||||
.Groups[1]
|
||||
.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(trackId))
|
||||
return trackId;
|
||||
|
||||
|
||||
@@ -99,7 +99,8 @@ public partial record Message
|
||||
if (trailingEmbeds.Any())
|
||||
{
|
||||
// Concatenate all images into one embed
|
||||
var images = embed.Images
|
||||
var images = embed
|
||||
.Images
|
||||
.Concat(trailingEmbeds.SelectMany(e => e.Images))
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -40,10 +40,12 @@ public class DiscordClient
|
||||
|
||||
// Don't validate because the token can have special characters
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/828
|
||||
request.Headers.TryAddWithoutValidation(
|
||||
"Authorization",
|
||||
tokenKind == TokenKind.Bot ? $"Bot {_token}" : _token
|
||||
);
|
||||
request
|
||||
.Headers
|
||||
.TryAddWithoutValidation(
|
||||
"Authorization",
|
||||
tokenKind == TokenKind.Bot ? $"Bot {_token}" : _token
|
||||
);
|
||||
|
||||
var response = await Http.Client.SendAsync(
|
||||
request,
|
||||
@@ -58,11 +60,13 @@ public class DiscordClient
|
||||
// require properly keeping track of Discord's global/per-route/per-resource
|
||||
// rate limits and that's just way too much effort.
|
||||
// https://discord.com/developers/docs/topics/rate-limits
|
||||
var remainingRequestCount = response.Headers
|
||||
var remainingRequestCount = response
|
||||
.Headers
|
||||
.TryGetValue("X-RateLimit-Remaining")
|
||||
?.Pipe(s => int.Parse(s, CultureInfo.InvariantCulture));
|
||||
|
||||
var resetAfterDelay = response.Headers
|
||||
var resetAfterDelay = response
|
||||
.Headers
|
||||
.TryGetValue("X-RateLimit-Reset-After")
|
||||
?.Pipe(s => double.Parse(s, CultureInfo.InvariantCulture))
|
||||
.Pipe(TimeSpan.FromSeconds);
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AsyncKeyedLock" Version="6.2.2" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="Polly" Version="8.0.0" />
|
||||
<PackageReference Include="Polly" Version="8.1.0" />
|
||||
<PackageReference Include="RazorBlade" Version="0.4.4" />
|
||||
<PackageReference Include="Superpower" Version="3.0.0" />
|
||||
<PackageReference Include="WebMarkupMin.Core" Version="2.14.0" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.3.6" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.3.7" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -88,7 +88,7 @@ internal partial class CsvMessageWriter : MessageWriter
|
||||
await _writer.WriteAsync(',');
|
||||
|
||||
// Message timestamp
|
||||
await _writer.WriteAsync(CsvEncode(Context.FormatDate(message.Timestamp)));
|
||||
await _writer.WriteAsync(CsvEncode(Context.FormatDate(message.Timestamp, "o")));
|
||||
await _writer.WriteAsync(',');
|
||||
|
||||
// Message content
|
||||
|
||||
@@ -65,7 +65,9 @@ internal partial class ExportAssetDownloader
|
||||
// Try to set the file date according to the last-modified header
|
||||
try
|
||||
{
|
||||
var lastModified = response.Content.Headers
|
||||
var lastModified = response
|
||||
.Content
|
||||
.Headers
|
||||
.TryGetValue("Last-Modified")
|
||||
?.Pipe(
|
||||
s =>
|
||||
|
||||
@@ -101,7 +101,8 @@ internal class ExportContext
|
||||
public Role? TryGetRole(Snowflake id) => _rolesById.GetValueOrDefault(id);
|
||||
|
||||
public IReadOnlyList<Role> GetUserRoles(Snowflake id) =>
|
||||
TryGetMember(id)?.RoleIds
|
||||
TryGetMember(id)
|
||||
?.RoleIds
|
||||
.Select(TryGetRole)
|
||||
.WhereNotNull()
|
||||
.OrderByDescending(r => r.Position)
|
||||
|
||||
@@ -182,10 +182,9 @@ public partial class ExportRequest
|
||||
=> before?.ToDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
|
||||
?? "",
|
||||
"%d"
|
||||
=> DateTimeOffset.Now.ToString(
|
||||
"yyyy-MM-dd",
|
||||
CultureInfo.InvariantCulture
|
||||
),
|
||||
=> DateTimeOffset
|
||||
.Now
|
||||
.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
|
||||
|
||||
"%%" => "%",
|
||||
_ => m.Value
|
||||
|
||||
@@ -26,12 +26,14 @@ internal class ContainsMessageFilter : MessageFilter
|
||||
|
||||
public override bool IsMatch(Message message) =>
|
||||
IsMatch(message.Content)
|
||||
|| message.Embeds.Any(
|
||||
e =>
|
||||
IsMatch(e.Title)
|
||||
|| IsMatch(e.Author?.Name)
|
||||
|| IsMatch(e.Description)
|
||||
|| IsMatch(e.Footer?.Text)
|
||||
|| e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value))
|
||||
);
|
||||
|| message
|
||||
.Embeds
|
||||
.Any(
|
||||
e =>
|
||||
IsMatch(e.Title)
|
||||
|| IsMatch(e.Author?.Name)
|
||||
|| IsMatch(e.Description)
|
||||
|| IsMatch(e.Footer?.Text)
|
||||
|| e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,13 @@ internal class MentionsMessageFilter : MessageFilter
|
||||
public MentionsMessageFilter(string value) => _value = value;
|
||||
|
||||
public override bool IsMatch(Message message) =>
|
||||
message.MentionedUsers.Any(
|
||||
user =>
|
||||
string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, user.DisplayName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
message
|
||||
.MentionedUsers
|
||||
.Any(
|
||||
user =>
|
||||
string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, user.DisplayName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,10 +11,16 @@ internal class ReactionMessageFilter : MessageFilter
|
||||
public ReactionMessageFilter(string value) => _value = value;
|
||||
|
||||
public override bool IsMatch(Message message) =>
|
||||
message.Reactions.Any(
|
||||
r =>
|
||||
string.Equals(_value, r.Emoji.Id?.ToString(), StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
message
|
||||
.Reactions
|
||||
.Any(
|
||||
r =>
|
||||
string.Equals(
|
||||
_value,
|
||||
r.Emoji.Id?.ToString(),
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
)
|
||||
|| string.Equals(_value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(_value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,7 +196,8 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||
// Try to extract the message ID if the link points to a Discord message
|
||||
var linkedMessageId = Regex
|
||||
.Match(link.Url, @"^https?://(?:discord|discordapp)\.com/channels/.*?/(\d+)/?$")
|
||||
.Groups[1].Value;
|
||||
.Groups[1]
|
||||
.Value;
|
||||
|
||||
_buffer.Append(
|
||||
!string.IsNullOrWhiteSpace(linkedMessageId)
|
||||
|
||||
@@ -143,6 +143,31 @@ internal class JsonMessageWriter : MessageWriter
|
||||
await _writer.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private async ValueTask WriteEmbedVideoAsync(
|
||||
EmbedVideo embedVideo,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
_writer.WriteStartObject();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(embedVideo.Url))
|
||||
{
|
||||
_writer.WriteString(
|
||||
"url",
|
||||
await Context.ResolveAssetUrlAsync(
|
||||
embedVideo.ProxyUrl ?? embedVideo.Url,
|
||||
cancellationToken
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
_writer.WriteNumber("width", embedVideo.Width);
|
||||
_writer.WriteNumber("height", embedVideo.Height);
|
||||
|
||||
_writer.WriteEndObject();
|
||||
await _writer.FlushAsync(cancellationToken);
|
||||
}
|
||||
|
||||
private async ValueTask WriteEmbedFooterAsync(
|
||||
EmbedFooter embedFooter,
|
||||
CancellationToken cancellationToken = default
|
||||
@@ -224,6 +249,12 @@ internal class JsonMessageWriter : MessageWriter
|
||||
await WriteEmbedImageAsync(embed.Image, cancellationToken);
|
||||
}
|
||||
|
||||
if (embed.Video is not null)
|
||||
{
|
||||
_writer.WritePropertyName("video");
|
||||
await WriteEmbedVideoAsync(embed.Video, cancellationToken);
|
||||
}
|
||||
|
||||
if (embed.Footer is not null)
|
||||
{
|
||||
_writer.WritePropertyName("footer");
|
||||
@@ -419,12 +450,14 @@ internal class JsonMessageWriter : MessageWriter
|
||||
|
||||
_writer.WriteStartArray("users");
|
||||
await foreach (
|
||||
var user in Context.Discord.GetMessageReactionsAsync(
|
||||
Context.Request.Channel.Id,
|
||||
message.Id,
|
||||
reaction.Emoji,
|
||||
cancellationToken
|
||||
)
|
||||
var user in Context
|
||||
.Discord
|
||||
.GetMessageReactionsAsync(
|
||||
Context.Request.Channel.Id,
|
||||
message.Id,
|
||||
reaction.Emoji,
|
||||
cancellationToken
|
||||
)
|
||||
)
|
||||
{
|
||||
_writer.WriteStartObject();
|
||||
|
||||
@@ -44,10 +44,10 @@ internal partial class MessageExporter : IAsyncDisposable
|
||||
// Ensure that the partition limit has not been reached
|
||||
if (
|
||||
_writer is not null
|
||||
&& _context.Request.PartitionLimit.IsReached(
|
||||
_writer.MessagesWritten,
|
||||
_writer.BytesWritten
|
||||
)
|
||||
&& _context
|
||||
.Request
|
||||
.PartitionLimit
|
||||
.IsReached(_writer.MessagesWritten, _writer.BytesWritten)
|
||||
)
|
||||
{
|
||||
await ResetWriterAsync(cancellationToken);
|
||||
|
||||
@@ -30,7 +30,8 @@ internal class AggregateMatcher<T> : IMatcher<T>
|
||||
|
||||
// If this match is earlier than previous earliest - replace
|
||||
if (
|
||||
earliestMatch is null || match.Segment.StartIndex < earliestMatch.Segment.StartIndex
|
||||
earliestMatch is null
|
||||
|| match.Segment.StartIndex < earliestMatch.Segment.StartIndex
|
||||
)
|
||||
earliestMatch = match;
|
||||
|
||||
|
||||
@@ -24,12 +24,10 @@ internal class StringMatcher<T> : IMatcher<T>
|
||||
|
||||
public ParsedMatch<T>? TryMatch(StringSegment segment)
|
||||
{
|
||||
var index = segment.Source.IndexOf(
|
||||
_needle,
|
||||
segment.StartIndex,
|
||||
segment.Length,
|
||||
_comparison
|
||||
);
|
||||
var index = segment
|
||||
.Source
|
||||
.IndexOf(_needle, segment.StartIndex, segment.Length, _comparison);
|
||||
|
||||
if (index < 0)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ using DiscordChatExporter.Gui.ViewModels;
|
||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
|
||||
#if !DEBUG
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cogwheel" Version="2.0.4" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.25.0" PrivateAssets="all" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.26.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.75" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
<PackageReference Include="Onova" Version="2.6.10" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="all" />
|
||||
|
||||
@@ -66,7 +66,8 @@ public partial class SettingsService
|
||||
{
|
||||
try
|
||||
{
|
||||
return Registry.CurrentUser
|
||||
return Registry
|
||||
.CurrentUser
|
||||
.OpenSubKey(
|
||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
false
|
||||
|
||||
@@ -12,8 +12,8 @@ using DiscordChatExporter.Gui.Models;
|
||||
using DiscordChatExporter.Gui.Services;
|
||||
using DiscordChatExporter.Gui.Utils;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
using DiscordChatExporter.Gui.ViewModels.Messages;
|
||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||
using DiscordChatExporter.Gui.ViewModels.Messages;
|
||||
using Gress;
|
||||
using Gress.Completable;
|
||||
using Stylet;
|
||||
@@ -234,7 +234,8 @@ public class DashboardViewModel : PropertyChangedBase
|
||||
|
||||
var exporter = new ChannelExporter(_discord);
|
||||
|
||||
var channelProgressPairs = dialog.Channels!
|
||||
var channelProgressPairs = dialog
|
||||
.Channels!
|
||||
.Select(c => new { Channel = c, Progress = _progressMuxer.CreateInput() })
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ using DiscordChatExporter.Gui.Services;
|
||||
using DiscordChatExporter.Gui.Utils;
|
||||
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
using DiscordChatExporter.Gui.ViewModels.Messages;
|
||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||
using DiscordChatExporter.Gui.ViewModels.Messages;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Stylet;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user