mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-09 15:39:34 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f8749a105 | |||
| 4896d748aa | |||
| 4876ed192a | |||
| 4f6e6f4f0b | |||
| 8c75f04115 | |||
| df04b123cd | |||
| 578ed9be17 | |||
| ebceacc707 | |||
| eaab5786f2 | |||
| ebf7d65334 | |||
| 52a23828ce | |||
| 2edd781e32 | |||
| 19cdf47c6e | |||
| 65e0a63bda | |||
| 692438d10c | |||
| c607c6f307 | |||
| aae43821e7 | |||
| 7b9a4e1aff | |||
| d8e57f94a8 | |||
| 7b7df564ed |
@@ -15,6 +15,12 @@ Please follow the [instructions provided here](https://github.com/Tyrrrz/Discord
|
|||||||
dotnet DiscordChatExporter.Cli.dll export -t TOKEN -c CHANNEL
|
dotnet DiscordChatExporter.Cli.dll export -t TOKEN -c CHANNEL
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If the above command throws a "Permission denied" error, execute this command to fix the permissions:
|
||||||
|
|
||||||
|
```console
|
||||||
|
chmod 644 *.dll DiscordChatExporter.*
|
||||||
|
```
|
||||||
|
|
||||||
> [How to get Token and Channel IDs](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs/Token-and-IDs.md).
|
> [How to get Token and Channel IDs](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs/Token-and-IDs.md).
|
||||||
|
|
||||||
There's much more you can do with DCE.CLI! Read the [CLI explained](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs/Getting-started.md#dcecli-commands-) page to get started.
|
There's much more you can do with DCE.CLI! Read the [CLI explained](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs/Getting-started.md#dcecli-commands-) page to get started.
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
name: docker
|
name: docker
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# Outputs from this job aren't really used, but it's here to verify that
|
||||||
|
# the Dockerfile builds correctly on pull requests.
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -29,9 +33,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: DiscordChatExporter.Cli.Docker
|
name: DiscordChatExporter.Cli.Docker
|
||||||
path: DiscordChatExporter.Cli.Docker.tar
|
path: DiscordChatExporter.Cli.Docker.tar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
deploy-latest:
|
deploy:
|
||||||
if: ${{ github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'master' }}
|
# Deploy to DockerHub only on tag push or master branch push
|
||||||
|
if: ${{ github.event_name == 'push' && (github.ref_type == 'tag' || github.ref_type == 'branch' && github.ref_name == 'master') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -55,32 +61,6 @@ jobs:
|
|||||||
--platform linux/amd64,linux/arm64
|
--platform linux/amd64,linux/arm64
|
||||||
--push
|
--push
|
||||||
--tag tyrrrz/discordchatexporter:latest
|
--tag tyrrrz/discordchatexporter:latest
|
||||||
|
${{ github.ref_type == 'tag' && '--tag tyrrrz/discordchatexporter:$GITHUB_REF_NAME' || '' }}
|
||||||
|
${{ github.ref_type == 'tag' && '--tag tyrrrz/discordchatexporter:stable' || '' }}
|
||||||
.
|
.
|
||||||
|
|
||||||
deploy-stable:
|
|
||||||
if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
|
||||||
|
|
||||||
- name: Install Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # 3.0.0
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
|
||||||
run: >
|
|
||||||
echo ${{ secrets.DOCKER_TOKEN }} |
|
|
||||||
docker login --username tyrrrz --password-stdin
|
|
||||||
|
|
||||||
- name: Build & push image
|
|
||||||
run: >
|
|
||||||
docker buildx build
|
|
||||||
--file DiscordChatExporter.Cli.dockerfile
|
|
||||||
--platform linux/amd64,linux/arm64
|
|
||||||
--push
|
|
||||||
--tag tyrrrz/discordchatexporter:stable
|
|
||||||
--tag tyrrrz/discordchatexporter:${{ github.ref_name }}
|
|
||||||
.
|
|
||||||
+62
-54
@@ -1,6 +1,8 @@
|
|||||||
name: main
|
name: main
|
||||||
|
|
||||||
on: [push, pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||||
@@ -25,6 +27,8 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
# Tests need access to secrets, so we can't run them against PRs because of limited trust
|
# Tests need access to secrets, so we can't run them against PRs because of limited trust
|
||||||
if: ${{ github.event_name != 'pull_request' }}
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
env:
|
||||||
|
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||||
run: >
|
run: >
|
||||||
dotnet test
|
dotnet test
|
||||||
--configuration Release
|
--configuration Release
|
||||||
@@ -33,8 +37,6 @@ jobs:
|
|||||||
--
|
--
|
||||||
RunConfiguration.CollectSourceInformation=true
|
RunConfiguration.CollectSourceInformation=true
|
||||||
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
|
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
|
||||||
env:
|
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
|
||||||
|
|
||||||
- name: Upload coverage
|
- name: Upload coverage
|
||||||
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 3.1.4
|
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 3.1.4
|
||||||
@@ -42,6 +44,12 @@ jobs:
|
|||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
|
||||||
pack:
|
pack:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
app:
|
||||||
|
- DiscordChatExporter.Cli
|
||||||
|
- DiscordChatExporter.Gui
|
||||||
|
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: write
|
actions: write
|
||||||
@@ -56,80 +64,80 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: 7.0.x
|
dotnet-version: 7.0.x
|
||||||
|
|
||||||
- name: Publish (CLI)
|
- name: Publish app
|
||||||
run: >
|
run: >
|
||||||
dotnet publish DiscordChatExporter.Cli
|
dotnet publish ${{ matrix.app }}
|
||||||
--output DiscordChatExporter.Cli/publish/
|
--output ${{ matrix.app }}/bin/publish/
|
||||||
--configuration Release
|
--configuration Release
|
||||||
|
|
||||||
- name: Publish (GUI)
|
- name: Upload artifacts
|
||||||
run: >
|
|
||||||
dotnet publish DiscordChatExporter.Gui
|
|
||||||
--output DiscordChatExporter.Gui/publish/
|
|
||||||
--configuration Release
|
|
||||||
|
|
||||||
- name: Upload artifacts (CLI)
|
|
||||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.3
|
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.3
|
||||||
with:
|
with:
|
||||||
name: DiscordChatExporter.Cli
|
name: ${{ matrix.app }}
|
||||||
path: DiscordChatExporter.Cli/publish/
|
path: ${{ matrix.app }}/bin/publish/
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
- name: Upload artifacts (GUI)
|
release:
|
||||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.3
|
|
||||||
with:
|
|
||||||
name: DiscordChatExporter
|
|
||||||
path: DiscordChatExporter.Gui/publish/
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
|
if: ${{ github.event_name == 'push' && github.ref_type == 'tag' }}
|
||||||
|
|
||||||
needs:
|
needs:
|
||||||
- test
|
- test
|
||||||
- pack
|
- pack
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Create release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: >
|
||||||
|
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:
|
||||||
|
- DiscordChatExporter.Cli
|
||||||
|
- DiscordChatExporter.Gui
|
||||||
|
include:
|
||||||
|
- app: DiscordChatExporter.Cli
|
||||||
|
asset: DiscordChatExporter.Cli
|
||||||
|
- app: DiscordChatExporter.Gui
|
||||||
|
# GUI asset isn't suffixed, unlike the CLI asset
|
||||||
|
asset: DiscordChatExporter
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download artifacts (CLI)
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # 3.0.2
|
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # 3.0.2
|
||||||
with:
|
with:
|
||||||
name: DiscordChatExporter.Cli
|
name: ${{ matrix.app }}
|
||||||
path: DiscordChatExporter.Cli
|
path: ${{ matrix.app }}/
|
||||||
|
|
||||||
- name: Download artifacts (GUI)
|
- name: Create package
|
||||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # 3.0.2
|
# Change into the artifacts directory to avoid including the directory itself in the zip archive
|
||||||
with:
|
working-directory: ${{ matrix.app }}/
|
||||||
name: DiscordChatExporter
|
run: zip -r ../${{ matrix.asset }}.zip .
|
||||||
path: DiscordChatExporter.Gui
|
|
||||||
|
|
||||||
- name: Create package (CLI)
|
- name: Upload release asset
|
||||||
shell: pwsh
|
|
||||||
run: >
|
|
||||||
Compress-Archive
|
|
||||||
-Path DiscordChatExporter.Cli/*
|
|
||||||
-DestinationPath DiscordChatExporter.Cli.zip
|
|
||||||
-Force
|
|
||||||
|
|
||||||
- name: Create package (GUI)
|
|
||||||
shell: pwsh
|
|
||||||
run: >
|
|
||||||
Compress-Archive
|
|
||||||
-Path DiscordChatExporter.Gui/*
|
|
||||||
-DestinationPath DiscordChatExporter.zip
|
|
||||||
-Force
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: >
|
run: >
|
||||||
gh release create "${{ github.ref_name }}"
|
gh release upload ${{ github.ref_name }}
|
||||||
"DiscordChatExporter.Cli.zip"
|
${{ matrix.asset }}.zip
|
||||||
"DiscordChatExporter.zip"
|
--repo ${{ github.event.repository.full_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
|
|
||||||
|
|
||||||
notify:
|
notify:
|
||||||
needs: deploy
|
needs: deploy
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# 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:
|
||||||
|
- Fixed an issue where trying to export a channel that contained application interactions with empty command names crashed the application.
|
||||||
|
|
||||||
## v2.42.1 (19-Oct-2023)
|
## v2.42.1 (19-Oct-2023)
|
||||||
|
|
||||||
- General changes:
|
- General changes:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<Version>2.42.1</Version>
|
<Version>2.42.3</Version>
|
||||||
<Company>Tyrrrz</Company>
|
<Company>Tyrrrz</Company>
|
||||||
<Copyright>Copyright (c) Oleksii Holub</Copyright>
|
<Copyright>Copyright (c) Oleksii Holub</Copyright>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
|
|||||||
@@ -11,19 +11,19 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AngleSharp" Version="1.0.4" />
|
<PackageReference Include="AngleSharp" Version="1.0.5" />
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.0" PrivateAssets="all" />
|
<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="FluentAssertions" Version="6.12.0" />
|
||||||
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
|
<PackageReference Include="GitHubActionsTestLogger" Version="2.3.3" PrivateAssets="all" />
|
||||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.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.EnvironmentVariables" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" 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="ReflectionMagic" Version="5.0.0" />
|
||||||
<PackageReference Include="xunit" Version="2.5.1" />
|
<PackageReference Include="xunit" Version="2.6.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.1" PrivateAssets="all" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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.
|
# we can build the assembly for the correct platform.
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /tmp/dce
|
||||||
|
|
||||||
COPY favicon.ico .
|
COPY favicon.ico .
|
||||||
COPY NuGet.config .
|
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
|
# Publish a self-contained assembly so we can use a slimmer runtime image
|
||||||
RUN dotnet publish DiscordChatExporter.Cli \
|
RUN dotnet publish DiscordChatExporter.Cli \
|
||||||
--configuration Release \
|
|
||||||
-p:CSharpier_Bypass=true \
|
-p:CSharpier_Bypass=true \
|
||||||
|
--configuration Release \
|
||||||
--self-contained \
|
--self-contained \
|
||||||
--use-current-runtime \
|
--use-current-runtime \
|
||||||
--arch $TARGETARCH \
|
--arch $TARGETARCH \
|
||||||
--output publish/
|
--output DiscordChatExporter.Cli/bin/publish/
|
||||||
|
|
||||||
# -- Run
|
# -- Run
|
||||||
# Use `runtime-deps` instead of `runtime` because we have a self-contained assembly
|
# 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
|
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
|
# 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
|
# https://github.com/Tyrrrz/DiscordChatExporter/issues/851
|
||||||
RUN adduser --disabled-password --no-create-home dce
|
RUN adduser --disabled-password --no-create-home dce
|
||||||
@@ -42,5 +49,5 @@ USER dce
|
|||||||
# stays the same for backwards compatibility.
|
# stays the same for backwards compatibility.
|
||||||
WORKDIR /out
|
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"]
|
ENTRYPOINT ["/opt/dce/DiscordChatExporter.Cli"]
|
||||||
@@ -37,11 +37,13 @@ public abstract class DiscordCommandBase : ICommand
|
|||||||
{
|
{
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkYellow))
|
using (console.WithForegroundColor(ConsoleColor.DarkYellow))
|
||||||
{
|
{
|
||||||
console.Error.WriteLine(
|
console
|
||||||
"Warning: Option --bot is deprecated and should not be used. "
|
.Error
|
||||||
+ "The type of the provided token is now inferred automatically. "
|
.WriteLine(
|
||||||
+ "Please update your workflows as this option may be completely removed in a future version."
|
"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
|
#pragma warning restore CS0618
|
||||||
|
|||||||
@@ -244,9 +244,11 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||||||
// Print the result
|
// Print the result
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
{
|
{
|
||||||
await console.Output.WriteLineAsync(
|
await console
|
||||||
$"Successfully exported {channels.Count - errorsByChannel.Count} channel(s)."
|
.Output
|
||||||
);
|
.WriteLineAsync(
|
||||||
|
$"Successfully exported {channels.Count - errorsByChannel.Count} channel(s)."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print errors
|
// Print errors
|
||||||
@@ -256,9 +258,11 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||||||
|
|
||||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||||
{
|
{
|
||||||
await console.Error.WriteLineAsync(
|
await console
|
||||||
$"Failed to export {errorsByChannel.Count} the following channel(s):"
|
.Error
|
||||||
);
|
.WriteLineAsync(
|
||||||
|
$"Failed to export {errorsByChannel.Count} the following channel(s):"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var (channel, error) in errorsByChannel)
|
foreach (var (channel, error) in errorsByChannel)
|
||||||
@@ -320,33 +324,51 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
|||||||
// Support Ukraine callout
|
// Support Ukraine callout
|
||||||
if (!IsUkraineSupportMessageDisabled)
|
if (!IsUkraineSupportMessageDisabled)
|
||||||
{
|
{
|
||||||
console.Output.WriteLine(
|
console
|
||||||
"┌────────────────────────────────────────────────────────────────────┐"
|
.Output
|
||||||
);
|
.WriteLine(
|
||||||
console.Output.WriteLine(
|
"┌────────────────────────────────────────────────────────────────────┐"
|
||||||
"│ Thank you for supporting Ukraine <3 │"
|
);
|
||||||
);
|
console
|
||||||
console.Output.WriteLine(
|
.Output
|
||||||
"│ │"
|
.WriteLine(
|
||||||
);
|
"│ Thank you for supporting Ukraine <3 │"
|
||||||
console.Output.WriteLine(
|
);
|
||||||
"│ As Russia wages a genocidal war against my country, │"
|
console
|
||||||
);
|
.Output
|
||||||
console.Output.WriteLine(
|
.WriteLine(
|
||||||
"│ I'm grateful to everyone who continues to │"
|
"│ │"
|
||||||
);
|
);
|
||||||
console.Output.WriteLine(
|
console
|
||||||
"│ stand with Ukraine in our fight for freedom. │"
|
.Output
|
||||||
);
|
.WriteLine(
|
||||||
console.Output.WriteLine(
|
"│ As Russia wages a genocidal war against my country, │"
|
||||||
"│ │"
|
);
|
||||||
);
|
console
|
||||||
console.Output.WriteLine(
|
.Output
|
||||||
"│ Learn more: https://tyrrrz.me/ukraine │"
|
.WriteLine(
|
||||||
);
|
"│ I'm grateful to everyone who continues to │"
|
||||||
console.Output.WriteLine(
|
);
|
||||||
"└────────────────────────────────────────────────────────────────────┘"
|
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("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,9 +54,9 @@ public class ExportAllCommand : ExportCommandBase
|
|||||||
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
await foreach (var guild in Discord.GetUserGuildsAsync(cancellationToken))
|
||||||
{
|
{
|
||||||
// Regular channels
|
// Regular channels
|
||||||
await console.Output.WriteLineAsync(
|
await console
|
||||||
$"Fetching channels for guild '{guild.Name}'..."
|
.Output
|
||||||
);
|
.WriteLineAsync($"Fetching channels for guild '{guild.Name}'...");
|
||||||
|
|
||||||
var fetchedChannelsCount = 0;
|
var fetchedChannelsCount = 0;
|
||||||
await console
|
await console
|
||||||
@@ -94,9 +94,9 @@ public class ExportAllCommand : ExportCommandBase
|
|||||||
// Threads
|
// Threads
|
||||||
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
if (ThreadInclusionMode != ThreadInclusionMode.None)
|
||||||
{
|
{
|
||||||
await console.Output.WriteLineAsync(
|
await console
|
||||||
$"Fetching threads for guild '{guild.Name}'..."
|
.Output
|
||||||
);
|
.WriteLineAsync($"Fetching threads for guild '{guild.Name}'...");
|
||||||
|
|
||||||
var fetchedThreadsCount = 0;
|
var fetchedThreadsCount = 0;
|
||||||
await console
|
await console
|
||||||
@@ -126,9 +126,9 @@ public class ExportAllCommand : ExportCommandBase
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
await console.Output.WriteLineAsync(
|
await console
|
||||||
$"Fetched {fetchedThreadsCount} thread(s)."
|
.Output
|
||||||
);
|
.WriteLineAsync($"Fetched {fetchedThreadsCount} thread(s).");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,9 +180,9 @@ public class ExportAllCommand : ExportCommandBase
|
|||||||
|
|
||||||
using (console.WithForegroundColor(ConsoleColor.Red))
|
using (console.WithForegroundColor(ConsoleColor.Red))
|
||||||
{
|
{
|
||||||
await console.Error.WriteLineAsync(
|
await console
|
||||||
"Failed to access the following channel(s):"
|
.Error
|
||||||
);
|
.WriteLineAsync("Failed to access the following channel(s):");
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var dumpChannel in inaccessibleChannels)
|
foreach (var dumpChannel in inaccessibleChannels)
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ public class GetChannelsCommand : DiscordCommandBase
|
|||||||
foreach (var channel in channels)
|
foreach (var channel in channels)
|
||||||
{
|
{
|
||||||
// Channel ID
|
// Channel ID
|
||||||
await console.Output.WriteAsync(
|
await console
|
||||||
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
.Output
|
||||||
);
|
.WriteAsync(channel.Id.ToString().PadRight(channelIdMaxLength, ' '));
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
@@ -88,9 +88,11 @@ public class GetChannelsCommand : DiscordCommandBase
|
|||||||
await console.Output.WriteAsync(" * ");
|
await console.Output.WriteAsync(" * ");
|
||||||
|
|
||||||
// Thread ID
|
// Thread ID
|
||||||
await console.Output.WriteAsync(
|
await console
|
||||||
channelThread.Id.ToString().PadRight(channelThreadIdMaxLength, ' ')
|
.Output
|
||||||
);
|
.WriteAsync(
|
||||||
|
channelThread.Id.ToString().PadRight(channelThreadIdMaxLength, ' ')
|
||||||
|
);
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
@@ -106,9 +108,9 @@ public class GetChannelsCommand : DiscordCommandBase
|
|||||||
|
|
||||||
// Thread status
|
// Thread status
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
await console.Output.WriteLineAsync(
|
await console
|
||||||
channelThread.IsArchived ? "Archived" : "Active"
|
.Output
|
||||||
);
|
.WriteLineAsync(channelThread.IsArchived ? "Archived" : "Active");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ public class GetDirectChannelsCommand : DiscordCommandBase
|
|||||||
foreach (var channel in channels)
|
foreach (var channel in channels)
|
||||||
{
|
{
|
||||||
// Channel ID
|
// Channel ID
|
||||||
await console.Output.WriteAsync(
|
await console
|
||||||
channel.Id.ToString().PadRight(channelIdMaxLength, ' ')
|
.Output
|
||||||
);
|
.WriteAsync(channel.Id.ToString().PadRight(channelIdMaxLength, ' '));
|
||||||
|
|
||||||
// Separator
|
// Separator
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
using (console.WithForegroundColor(ConsoleColor.DarkGray))
|
||||||
|
|||||||
@@ -15,18 +15,20 @@ public class GuideCommand : ICommand
|
|||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
console.Output.WriteLine("To get user token:");
|
console.Output.WriteLine("To get user token:");
|
||||||
|
|
||||||
console.Output.WriteLine(
|
console
|
||||||
" * Automating user accounts is technically against TOS — USE AT YOUR OWN RISK!"
|
.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(" 1. Open Discord in your web browser and login");
|
||||||
console.Output.WriteLine(" 2. Open any server or direct message channel");
|
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(" 3. Press Ctrl+Shift+I to show developer tools");
|
||||||
console.Output.WriteLine(" 4. Navigate to the Network tab");
|
console.Output.WriteLine(" 4. Navigate to the Network tab");
|
||||||
console.Output.WriteLine(" 5. Press Ctrl+R to reload");
|
console.Output.WriteLine(" 5. Press Ctrl+R to reload");
|
||||||
console.Output.WriteLine(" 6. Switch between random channels to trigger network requests");
|
console.Output.WriteLine(" 6. Switch between random channels to trigger network requests");
|
||||||
console.Output.WriteLine(
|
console
|
||||||
" 7. Search for a request containing \"messages?limit=50\" or similar"
|
.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(" 8. Select the Headers tab on the right");
|
||||||
console.Output.WriteLine(" 9. Scroll down to the Request Headers section");
|
console.Output.WriteLine(" 9. Scroll down to the Request Headers section");
|
||||||
console.Output.WriteLine(" 10. Copy the value of the \"authorization\" header");
|
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(" 2. Open your application's settings");
|
||||||
console.Output.WriteLine(" 3. Navigate to the Bot section on the left");
|
console.Output.WriteLine(" 3. Navigate to the Bot section on the left");
|
||||||
console.Output.WriteLine(" 4. Under Token click Copy");
|
console.Output.WriteLine(" 4. Under Token click Copy");
|
||||||
console.Output.WriteLine(
|
console
|
||||||
" * Your bot needs to have Message Content Intent enabled to read messages"
|
.Output
|
||||||
);
|
.WriteLine(
|
||||||
|
" * Your bot needs to have Message Content Intent enabled to read messages"
|
||||||
|
);
|
||||||
console.Output.WriteLine();
|
console.Output.WriteLine();
|
||||||
|
|
||||||
// Guild or channel ID
|
// Guild or channel ID
|
||||||
@@ -53,20 +57,22 @@ public class GuideCommand : ICommand
|
|||||||
console.Output.WriteLine(" 2. Open Settings");
|
console.Output.WriteLine(" 2. Open Settings");
|
||||||
console.Output.WriteLine(" 3. Go to Advanced section");
|
console.Output.WriteLine(" 3. Go to Advanced section");
|
||||||
console.Output.WriteLine(" 4. Enable Developer Mode");
|
console.Output.WriteLine(" 4. Enable Developer Mode");
|
||||||
console.Output.WriteLine(
|
console
|
||||||
" 5. Right-click on the desired guild or channel and click Copy Server ID or Copy Channel ID"
|
.Output
|
||||||
);
|
.WriteLine(
|
||||||
|
" 5. Right-click on the desired guild or channel and click Copy Server ID or Copy Channel ID"
|
||||||
|
);
|
||||||
console.Output.WriteLine();
|
console.Output.WriteLine();
|
||||||
|
|
||||||
// Docs link
|
// Docs link
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
console.Output.WriteLine(
|
console
|
||||||
"If you have questions or issues, please refer to the documentation:"
|
.Output
|
||||||
);
|
.WriteLine("If you have questions or issues, please refer to the documentation:");
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
||||||
console.Output.WriteLine(
|
console
|
||||||
"https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs"
|
.Output
|
||||||
);
|
.WriteLine("https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs");
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CliFx" Version="2.3.4" />
|
<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="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
||||||
<PackageReference Include="Gress" Version="2.1.1" />
|
<PackageReference Include="Gress" Version="2.1.1" />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using JsonExtensions.Reading;
|
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using JsonExtensions.Reading;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||||
|
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ public partial record SpotifyTrackEmbedProjection
|
|||||||
private static string? TryParseTrackId(string embedUrl)
|
private static string? TryParseTrackId(string embedUrl)
|
||||||
{
|
{
|
||||||
// https://open.spotify.com/track/1LHZMWefF9502NPfArRfvP?si=3efac6ce9be04f0a
|
// https://open.spotify.com/track/1LHZMWefF9502NPfArRfvP?si=3efac6ce9be04f0a
|
||||||
var trackId = Regex.Match(embedUrl, @"spotify\.com/track/(.*?)(?:\?|&|/|$)").Groups[
|
var trackId = Regex
|
||||||
1
|
.Match(embedUrl, @"spotify\.com/track/(.*?)(?:\?|&|/|$)")
|
||||||
].Value;
|
.Groups[1]
|
||||||
|
.Value;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(trackId))
|
if (!string.IsNullOrWhiteSpace(trackId))
|
||||||
return trackId;
|
return trackId;
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public record Interaction(Snowflake Id, string Name, User User)
|
|||||||
public static Interaction Parse(JsonElement json)
|
public static Interaction Parse(JsonElement json)
|
||||||
{
|
{
|
||||||
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
|
||||||
var name = json.GetProperty("name").GetNonWhiteSpaceString();
|
var name = json.GetProperty("name").GetNonNullString(); // may be empty, but not null
|
||||||
var user = json.GetProperty("user").Pipe(User.Parse);
|
var user = json.GetProperty("user").Pipe(User.Parse);
|
||||||
|
|
||||||
return new Interaction(id, name, user);
|
return new Interaction(id, name, user);
|
||||||
|
|||||||
@@ -99,7 +99,8 @@ public partial record Message
|
|||||||
if (trailingEmbeds.Any())
|
if (trailingEmbeds.Any())
|
||||||
{
|
{
|
||||||
// Concatenate all images into one embed
|
// Concatenate all images into one embed
|
||||||
var images = embed.Images
|
var images = embed
|
||||||
|
.Images
|
||||||
.Concat(trailingEmbeds.SelectMany(e => e.Images))
|
.Concat(trailingEmbeds.SelectMany(e => e.Images))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,12 @@ public class DiscordClient
|
|||||||
|
|
||||||
// Don't validate because the token can have special characters
|
// Don't validate because the token can have special characters
|
||||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/828
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/828
|
||||||
request.Headers.TryAddWithoutValidation(
|
request
|
||||||
"Authorization",
|
.Headers
|
||||||
tokenKind == TokenKind.Bot ? $"Bot {_token}" : _token
|
.TryAddWithoutValidation(
|
||||||
);
|
"Authorization",
|
||||||
|
tokenKind == TokenKind.Bot ? $"Bot {_token}" : _token
|
||||||
|
);
|
||||||
|
|
||||||
var response = await Http.Client.SendAsync(
|
var response = await Http.Client.SendAsync(
|
||||||
request,
|
request,
|
||||||
@@ -58,11 +60,13 @@ public class DiscordClient
|
|||||||
// require properly keeping track of Discord's global/per-route/per-resource
|
// require properly keeping track of Discord's global/per-route/per-resource
|
||||||
// rate limits and that's just way too much effort.
|
// rate limits and that's just way too much effort.
|
||||||
// https://discord.com/developers/docs/topics/rate-limits
|
// https://discord.com/developers/docs/topics/rate-limits
|
||||||
var remainingRequestCount = response.Headers
|
var remainingRequestCount = response
|
||||||
|
.Headers
|
||||||
.TryGetValue("X-RateLimit-Remaining")
|
.TryGetValue("X-RateLimit-Remaining")
|
||||||
?.Pipe(s => int.Parse(s, CultureInfo.InvariantCulture));
|
?.Pipe(s => int.Parse(s, CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
var resetAfterDelay = response.Headers
|
var resetAfterDelay = response
|
||||||
|
.Headers
|
||||||
.TryGetValue("X-RateLimit-Reset-After")
|
.TryGetValue("X-RateLimit-Reset-After")
|
||||||
?.Pipe(s => double.Parse(s, CultureInfo.InvariantCulture))
|
?.Pipe(s => double.Parse(s, CultureInfo.InvariantCulture))
|
||||||
.Pipe(TimeSpan.FromSeconds);
|
.Pipe(TimeSpan.FromSeconds);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AsyncKeyedLock" Version="6.2.1" />
|
<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="Gress" Version="2.1.1" />
|
||||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
<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="RazorBlade" Version="0.4.4" />
|
||||||
<PackageReference Include="Superpower" Version="3.0.0" />
|
<PackageReference Include="Superpower" Version="3.0.0" />
|
||||||
<PackageReference Include="WebMarkupMin.Core" Version="2.14.0" />
|
<PackageReference Include="WebMarkupMin.Core" Version="2.14.0" />
|
||||||
<PackageReference Include="YoutubeExplode" Version="6.3.4" />
|
<PackageReference Include="YoutubeExplode" Version="6.3.7" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -88,7 +88,7 @@ internal partial class CsvMessageWriter : MessageWriter
|
|||||||
await _writer.WriteAsync(',');
|
await _writer.WriteAsync(',');
|
||||||
|
|
||||||
// Message timestamp
|
// Message timestamp
|
||||||
await _writer.WriteAsync(CsvEncode(Context.FormatDate(message.Timestamp)));
|
await _writer.WriteAsync(CsvEncode(Context.FormatDate(message.Timestamp, "o")));
|
||||||
await _writer.WriteAsync(',');
|
await _writer.WriteAsync(',');
|
||||||
|
|
||||||
// Message content
|
// Message content
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ internal partial class ExportAssetDownloader
|
|||||||
// Try to set the file date according to the last-modified header
|
// Try to set the file date according to the last-modified header
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lastModified = response.Content.Headers
|
var lastModified = response
|
||||||
|
.Content
|
||||||
|
.Headers
|
||||||
.TryGetValue("Last-Modified")
|
.TryGetValue("Last-Modified")
|
||||||
?.Pipe(
|
?.Pipe(
|
||||||
s =>
|
s =>
|
||||||
|
|||||||
@@ -101,7 +101,8 @@ internal class ExportContext
|
|||||||
public Role? TryGetRole(Snowflake id) => _rolesById.GetValueOrDefault(id);
|
public Role? TryGetRole(Snowflake id) => _rolesById.GetValueOrDefault(id);
|
||||||
|
|
||||||
public IReadOnlyList<Role> GetUserRoles(Snowflake id) =>
|
public IReadOnlyList<Role> GetUserRoles(Snowflake id) =>
|
||||||
TryGetMember(id)?.RoleIds
|
TryGetMember(id)
|
||||||
|
?.RoleIds
|
||||||
.Select(TryGetRole)
|
.Select(TryGetRole)
|
||||||
.WhereNotNull()
|
.WhereNotNull()
|
||||||
.OrderByDescending(r => r.Position)
|
.OrderByDescending(r => r.Position)
|
||||||
|
|||||||
@@ -182,10 +182,9 @@ public partial class ExportRequest
|
|||||||
=> before?.ToDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
|
=> before?.ToDate().ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)
|
||||||
?? "",
|
?? "",
|
||||||
"%d"
|
"%d"
|
||||||
=> DateTimeOffset.Now.ToString(
|
=> DateTimeOffset
|
||||||
"yyyy-MM-dd",
|
.Now
|
||||||
CultureInfo.InvariantCulture
|
.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture),
|
||||||
),
|
|
||||||
|
|
||||||
"%%" => "%",
|
"%%" => "%",
|
||||||
_ => m.Value
|
_ => m.Value
|
||||||
|
|||||||
@@ -26,12 +26,14 @@ internal class ContainsMessageFilter : MessageFilter
|
|||||||
|
|
||||||
public override bool IsMatch(Message message) =>
|
public override bool IsMatch(Message message) =>
|
||||||
IsMatch(message.Content)
|
IsMatch(message.Content)
|
||||||
|| message.Embeds.Any(
|
|| message
|
||||||
e =>
|
.Embeds
|
||||||
IsMatch(e.Title)
|
.Any(
|
||||||
|| IsMatch(e.Author?.Name)
|
e =>
|
||||||
|| IsMatch(e.Description)
|
IsMatch(e.Title)
|
||||||
|| IsMatch(e.Footer?.Text)
|
|| IsMatch(e.Author?.Name)
|
||||||
|| e.Fields.Any(f => IsMatch(f.Name) || IsMatch(f.Value))
|
|| 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 MentionsMessageFilter(string value) => _value = value;
|
||||||
|
|
||||||
public override bool IsMatch(Message message) =>
|
public override bool IsMatch(Message message) =>
|
||||||
message.MentionedUsers.Any(
|
message
|
||||||
user =>
|
.MentionedUsers
|
||||||
string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase)
|
.Any(
|
||||||
|| string.Equals(_value, user.DisplayName, StringComparison.OrdinalIgnoreCase)
|
user =>
|
||||||
|| string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase)
|
string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(_value, user.Id.ToString(), 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 ReactionMessageFilter(string value) => _value = value;
|
||||||
|
|
||||||
public override bool IsMatch(Message message) =>
|
public override bool IsMatch(Message message) =>
|
||||||
message.Reactions.Any(
|
message
|
||||||
r =>
|
.Reactions
|
||||||
string.Equals(_value, r.Emoji.Id?.ToString(), StringComparison.OrdinalIgnoreCase)
|
.Any(
|
||||||
|| string.Equals(_value, r.Emoji.Name, StringComparison.OrdinalIgnoreCase)
|
r =>
|
||||||
|| string.Equals(_value, r.Emoji.Code, StringComparison.OrdinalIgnoreCase)
|
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
|
// Try to extract the message ID if the link points to a Discord message
|
||||||
var linkedMessageId = Regex
|
var linkedMessageId = Regex
|
||||||
.Match(link.Url, @"^https?://(?:discord|discordapp)\.com/channels/.*?/(\d+)/?$")
|
.Match(link.Url, @"^https?://(?:discord|discordapp)\.com/channels/.*?/(\d+)/?$")
|
||||||
.Groups[1].Value;
|
.Groups[1]
|
||||||
|
.Value;
|
||||||
|
|
||||||
_buffer.Append(
|
_buffer.Append(
|
||||||
!string.IsNullOrWhiteSpace(linkedMessageId)
|
!string.IsNullOrWhiteSpace(linkedMessageId)
|
||||||
|
|||||||
@@ -143,6 +143,31 @@ internal class JsonMessageWriter : MessageWriter
|
|||||||
await _writer.FlushAsync(cancellationToken);
|
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(
|
private async ValueTask WriteEmbedFooterAsync(
|
||||||
EmbedFooter embedFooter,
|
EmbedFooter embedFooter,
|
||||||
CancellationToken cancellationToken = default
|
CancellationToken cancellationToken = default
|
||||||
@@ -224,6 +249,12 @@ internal class JsonMessageWriter : MessageWriter
|
|||||||
await WriteEmbedImageAsync(embed.Image, cancellationToken);
|
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)
|
if (embed.Footer is not null)
|
||||||
{
|
{
|
||||||
_writer.WritePropertyName("footer");
|
_writer.WritePropertyName("footer");
|
||||||
@@ -419,12 +450,14 @@ internal class JsonMessageWriter : MessageWriter
|
|||||||
|
|
||||||
_writer.WriteStartArray("users");
|
_writer.WriteStartArray("users");
|
||||||
await foreach (
|
await foreach (
|
||||||
var user in Context.Discord.GetMessageReactionsAsync(
|
var user in Context
|
||||||
Context.Request.Channel.Id,
|
.Discord
|
||||||
message.Id,
|
.GetMessageReactionsAsync(
|
||||||
reaction.Emoji,
|
Context.Request.Channel.Id,
|
||||||
cancellationToken
|
message.Id,
|
||||||
)
|
reaction.Emoji,
|
||||||
|
cancellationToken
|
||||||
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
_writer.WriteStartObject();
|
_writer.WriteStartObject();
|
||||||
|
|||||||
@@ -44,10 +44,10 @@ internal partial class MessageExporter : IAsyncDisposable
|
|||||||
// Ensure that the partition limit has not been reached
|
// Ensure that the partition limit has not been reached
|
||||||
if (
|
if (
|
||||||
_writer is not null
|
_writer is not null
|
||||||
&& _context.Request.PartitionLimit.IsReached(
|
&& _context
|
||||||
_writer.MessagesWritten,
|
.Request
|
||||||
_writer.BytesWritten
|
.PartitionLimit
|
||||||
)
|
.IsReached(_writer.MessagesWritten, _writer.BytesWritten)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
await ResetWriterAsync(cancellationToken);
|
await ResetWriterAsync(cancellationToken);
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ internal class AggregateMatcher<T> : IMatcher<T>
|
|||||||
|
|
||||||
// If this match is earlier than previous earliest - replace
|
// If this match is earlier than previous earliest - replace
|
||||||
if (
|
if (
|
||||||
earliestMatch is null || match.Segment.StartIndex < earliestMatch.Segment.StartIndex
|
earliestMatch is null
|
||||||
|
|| match.Segment.StartIndex < earliestMatch.Segment.StartIndex
|
||||||
)
|
)
|
||||||
earliestMatch = match;
|
earliestMatch = match;
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,10 @@ internal class StringMatcher<T> : IMatcher<T>
|
|||||||
|
|
||||||
public ParsedMatch<T>? TryMatch(StringSegment segment)
|
public ParsedMatch<T>? TryMatch(StringSegment segment)
|
||||||
{
|
{
|
||||||
var index = segment.Source.IndexOf(
|
var index = segment
|
||||||
_needle,
|
.Source
|
||||||
segment.StartIndex,
|
.IndexOf(_needle, segment.StartIndex, segment.Length, _comparison);
|
||||||
segment.Length,
|
|
||||||
_comparison
|
|
||||||
);
|
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using DiscordChatExporter.Gui.ViewModels;
|
|||||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
using StyletIoC;
|
using StyletIoC;
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Cogwheel" Version="2.0.3" />
|
<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="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.1" PrivateAssets="all" />
|
||||||
<PackageReference Include="Gress" Version="2.1.1" />
|
<PackageReference Include="Gress" Version="2.1.1" />
|
||||||
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
||||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
||||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
|
||||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||||
<PackageReference Include="Onova" Version="2.6.10" />
|
<PackageReference Include="Onova" Version="2.6.10" />
|
||||||
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="all" />
|
<PackageReference Include="PropertyChanged.Fody" Version="4.1.0" PrivateAssets="all" />
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ public partial class SettingsService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Registry.CurrentUser
|
return Registry
|
||||||
|
.CurrentUser
|
||||||
.OpenSubKey(
|
.OpenSubKey(
|
||||||
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ using DiscordChatExporter.Gui.Models;
|
|||||||
using DiscordChatExporter.Gui.Services;
|
using DiscordChatExporter.Gui.Services;
|
||||||
using DiscordChatExporter.Gui.Utils;
|
using DiscordChatExporter.Gui.Utils;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Messages;
|
|
||||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||||
|
using DiscordChatExporter.Gui.ViewModels.Messages;
|
||||||
using Gress;
|
using Gress;
|
||||||
using Gress.Completable;
|
using Gress.Completable;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
@@ -234,7 +234,8 @@ public class DashboardViewModel : PropertyChangedBase
|
|||||||
|
|
||||||
var exporter = new ChannelExporter(_discord);
|
var exporter = new ChannelExporter(_discord);
|
||||||
|
|
||||||
var channelProgressPairs = dialog.Channels!
|
var channelProgressPairs = dialog
|
||||||
|
.Channels!
|
||||||
.Select(c => new { Channel = c, Progress = _progressMuxer.CreateInput() })
|
.Select(c => new { Channel = c, Progress = _progressMuxer.CreateInput() })
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ using DiscordChatExporter.Gui.Services;
|
|||||||
using DiscordChatExporter.Gui.Utils;
|
using DiscordChatExporter.Gui.Utils;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Components;
|
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Messages;
|
|
||||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||||
|
using DiscordChatExporter.Gui.ViewModels.Messages;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ The following table lists all available download options:
|
|||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul>
|
||||||
<li>🟢 <b><a href="https://github.com/Tyrrrz/DiscordChatExporter/releases/latest">Stable release</a></b> (<code>DiscordChatExporter.zip</code>)</li>
|
<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/main.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.Gui.zip</code>)</li>
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -71,8 +71,8 @@ The following table lists all available download options:
|
|||||||
<td><b>CLI</b></td>
|
<td><b>CLI</b></td>
|
||||||
<td>
|
<td>
|
||||||
<ul>
|
<ul>
|
||||||
<li>🟢 <b><a href="https://github.com/Tyrrrz/DiscordChatExporter/releases/latest">Stable release</a></b> (<code>DiscordChatExporter.CLI.zip</code></li>
|
<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/main.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://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>
|
<li>📦 <a href="https://aur.archlinux.org/packages/discord-chat-exporter-cli">AUR</a> (<code>discord-chat-exporter-cli</code>)</li>
|
||||||
<li>📦 <a href="https://search.nixos.org/packages?query=discordchatexporter-cli">Nix</a> (<code>discordchatexporter-cli</code>)</li>
|
<li>📦 <a href="https://search.nixos.org/packages?query=discordchatexporter-cli">Nix</a> (<code>discordchatexporter-cli</code>)</li>
|
||||||
|
|||||||
Reference in New Issue
Block a user