mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-07 14:44:39 +02:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 72f02cd9a0 | |||
| 47d560229d | |||
| 0e87098889 | |||
| 0139eeaeed | |||
| c0ea57e9b1 | |||
| 7c04db40f8 | |||
| 4588bd0496 | |||
| 3a22db14a7 | |||
| ba72927c37 | |||
| 822bab2365 | |||
| 211db11041 |
@@ -61,6 +61,17 @@ For more information, please refer to the [Dockerfile](https://github.com/Tyrrrz
|
||||
|
||||
To get your Token and Channel IDs, please refer to [this page](Token-and-IDs.md).
|
||||
|
||||
## Unix permissions issues
|
||||
|
||||
This image was designed with a user running as uid:gid of 1000:1000.
|
||||
|
||||
If your current user has different IDs, and you want to generate files directly editable for your user, you might want to run the container like this:
|
||||
|
||||
```console
|
||||
mkdir data # or chown -R $(id -u):$(id -g) data
|
||||
docker run -it --rm -v $PWD/data:/out --user $(id -u):$(id -g) tyrrrz/discordchatexporter:stable export -t TOKEN -g CHANNELID
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
DiscordChatExpoter CLI accepts the `DISCORD_TOKEN` environment variable as a fallback for the `--token` option. You can set this variable either with the `--env` Docker option or with a combination of the `--env-file` Docker option and a `.env` file.
|
||||
|
||||
+13
-7
@@ -8,19 +8,25 @@ Please follow the [instructions provided here](Dotnet.md).
|
||||
|
||||
1. Download [DiscordChatExporter.CLI.zip](https://github.com/Tyrrrz/DiscordChatExporter/releases/latest) and extract it to a folder.
|
||||
2. Open Terminal.
|
||||
3. `cd` into the extracted folder. You can do this in Terminal by typing `cd`, then press the SPACE key, drag and drop the extracted folder into the Terminal window, and press the ENTER key.
|
||||
3. Change the working directory into the extracted folder. You can do this in Terminal by typing `cd`, then press the SPACE key, drag and drop the extracted folder into the Terminal window, and press the ENTER key.
|
||||
4. Replace `TOKEN` and `CHANNEL`, then execute this command to export:
|
||||
|
||||
```console
|
||||
./DiscordChatExporter.Cli.sh export -t TOKEN -c CHANNEL
|
||||
```
|
||||
|
||||
If the above command throws a "Permission denied" error, use `chmod` to fix the permissions:
|
||||
|
||||
```console
|
||||
chmod +x DiscordChatExporter.Cli.sh
|
||||
```
|
||||
|
||||
Alternatively, if the script doesn't work, you can run the following command to run the application directly:
|
||||
|
||||
```console
|
||||
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](Token-and-IDs.md).
|
||||
|
||||
There's much more you can do with DCE.CLI! Read the [CLI explained](Using-the-CLI.md) page to get started.
|
||||
|
||||
@@ -13,6 +13,18 @@ Please follow the [instructions provided here](Dotnet.md).
|
||||
3. In the Terminal window, type `cd` , press the SPACE key, then drag and drop the extracted folder into the window, then press the RETURN key.
|
||||
4. Execute the following command to export, replacing `TOKEN` and `CHANNEL` with your own values:
|
||||
|
||||
```console
|
||||
./DiscordChatExporter.Cli.sh export -t TOKEN -c CHANNEL
|
||||
```
|
||||
|
||||
If the above command throws a "Permission denied" error, use `chmod` to fix the permissions:
|
||||
|
||||
```console
|
||||
chmod +x DiscordChatExporter.Cli.sh
|
||||
```
|
||||
|
||||
Alternatively, if the script doesn't work, you can run the following command to run the application directly:
|
||||
|
||||
```console
|
||||
dotnet DiscordChatExporter.Cli.dll export -t TOKEN -c CHANNEL
|
||||
```
|
||||
|
||||
+13
-1
@@ -28,7 +28,19 @@ You can also drag and drop the folder on **every platform**.
|
||||
Now we're ready to run the commands. The examples on this page follow the Windows file path format, change the file
|
||||
paths according to your system.
|
||||
|
||||
Type the following in Command Prompt (Terminal), then press ENTER to run it. This will list DCE's options.
|
||||
On **Windows**, type the following command in your terminal of choice, then press ENTER to run it. This will list all available subcommands and options.
|
||||
|
||||
```console
|
||||
DiscordChatExporter.Cli.exe
|
||||
```
|
||||
|
||||
On **macOS** and **Linux**, run the following instead:
|
||||
|
||||
```console
|
||||
./DiscordChatExporter.Cli.sh
|
||||
```
|
||||
|
||||
If either of the above approaches don't work for you, you can also run **DiscordChatExporter** directly like this, regardless of the platform:
|
||||
|
||||
```console
|
||||
dotnet DiscordChatExporter.Cli.dll
|
||||
|
||||
@@ -24,10 +24,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Install Docker Buildx
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # 3.0.0
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
|
||||
- name: Build image
|
||||
run: >
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
--output type=tar,dest=DiscordChatExporter.Cli.Docker.tar
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.3
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
name: DiscordChatExporter.Cli.Docker
|
||||
path: DiscordChatExporter.Cli.Docker.tar
|
||||
@@ -56,10 +56,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Install Docker Buildx
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # 3.0.0
|
||||
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
|
||||
- name: Login to DockerHub
|
||||
run: >
|
||||
|
||||
+15
-10
@@ -26,10 +26,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Install .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # 3.2.0
|
||||
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
@@ -51,10 +51,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Install .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # 3.2.0
|
||||
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover
|
||||
|
||||
- name: Upload coverage
|
||||
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # 3.1.4
|
||||
uses: codecov/codecov-action@f30e4959ba63075080d4f7f90cacc18d9f3fafd7 # v4.0.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -92,10 +92,10 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # 4.1.0
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Install .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # 3.2.0
|
||||
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
--configuration Release
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # 3.1.3
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
name: ${{ matrix.app }}
|
||||
path: ${{ matrix.app }}/bin/publish/
|
||||
@@ -163,15 +163,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # 3.0.2
|
||||
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
|
||||
with:
|
||||
name: ${{ matrix.app }}
|
||||
path: ${{ matrix.app }}/
|
||||
|
||||
- name: Configure permissions
|
||||
# Remove this when the GUI also becomes cross-platform
|
||||
if: ${{ matrix.app == 'DiscordChatExporter.Cli' }}
|
||||
run: chmod +x ${{ matrix.app }}/${{ matrix.app }}.sh
|
||||
|
||||
- name: Create package
|
||||
# Change into the artifacts directory to avoid including the directory itself in the zip archive
|
||||
working-directory: ${{ matrix.app }}/
|
||||
run: zip -r ../${{ matrix.asset }}.zip .
|
||||
run: zip -rX ../${{ matrix.asset }}.zip .
|
||||
|
||||
- name: Upload release asset
|
||||
env:
|
||||
|
||||
@@ -150,6 +150,26 @@ public class HtmlEmbedSpecs
|
||||
iframeUrl.Should().StartWith("https://open.spotify.com/embed/track/1LHZMWefF9502NPfArRfvP");
|
||||
}
|
||||
|
||||
[Fact(Skip = "Twitch does not allow embeds from inside local HTML files")]
|
||||
public async Task I_can_export_a_channel_that_contains_a_message_with_a_Twitch_clip_embed()
|
||||
{
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1196
|
||||
|
||||
// Act
|
||||
var message = await ExportWrapper.GetMessageAsHtmlAsync(
|
||||
ChannelIds.EmbedTestCases,
|
||||
Snowflake.Parse("1207002986128216074")
|
||||
);
|
||||
|
||||
// Assert
|
||||
var iframeUrl = message.QuerySelector("iframe")?.GetAttribute("src");
|
||||
iframeUrl
|
||||
.Should()
|
||||
.StartWith(
|
||||
"https://clips.twitch.tv/embed?clip=SpicyMildCiderThisIsSparta--PQhbllrvej_Ee7v"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task I_can_export_a_channel_that_contains_a_message_with_a_YouTube_video_embed()
|
||||
{
|
||||
|
||||
@@ -40,20 +40,21 @@ LABEL org.opencontainers.image.licenses="MIT"
|
||||
|
||||
# Alpine image doesn't come with the ICU libraries pre-installed, so we need to install them manually.
|
||||
# We need the full ICU data because we allow the user to specify any locale for formatting purposes.
|
||||
RUN apk add --no-cache icu-libs
|
||||
RUN apk add --no-cache icu-data-full
|
||||
RUN apk add --no-cache icu-libs icu-data-full
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
# Use a non-root user to ensure that the files shared with the host are accessible by the host user
|
||||
# https://github.com/Tyrrrz/DiscordChatExporter/issues/851
|
||||
RUN adduser --disabled-password --no-create-home dce
|
||||
USER dce
|
||||
# https://github.com/Tyrrrz/DiscordChatExporter/issues/1174
|
||||
RUN apk add --no-cache su-exec
|
||||
RUN addgroup -S -g 1000 dce && adduser -S -H -G dce -u 1000 dce
|
||||
|
||||
# This directory is exposed to the user for mounting purposes, so it's important that it always
|
||||
# stays the same for backwards compatibility.
|
||||
WORKDIR /out
|
||||
|
||||
COPY --from=build /tmp/app/DiscordChatExporter.Cli/bin/publish /opt/app
|
||||
ENTRYPOINT ["/opt/app/DiscordChatExporter.Cli"]
|
||||
COPY docker-entrypoint.sh /opt/app
|
||||
ENTRYPOINT ["/opt/app/docker-entrypoint.sh"]
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="DiscordChatExporter.Cli.sh" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliFx" Version="2.3.5" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.26.7" PrivateAssets="all" />
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
dotnet "$(dirname "$0")/DiscordChatExporter.Cli.dll" "$@"
|
||||
@@ -31,6 +31,9 @@ public partial record Embed(
|
||||
public SpotifyTrackEmbedProjection? TryGetSpotifyTrack() =>
|
||||
SpotifyTrackEmbedProjection.TryResolve(this);
|
||||
|
||||
public TwitchClipEmbedProjection? TryGetTwitchClip() =>
|
||||
TwitchClipEmbedProjection.TryResolve(this);
|
||||
|
||||
public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() =>
|
||||
YouTubeVideoEmbedProjection.TryResolve(this);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Discord.Data.Embeds;
|
||||
|
||||
public partial record TwitchClipEmbedProjection(string ClipId)
|
||||
{
|
||||
public string Url => $"https://clips.twitch.tv/embed?clip={ClipId}&parent=localhost";
|
||||
}
|
||||
|
||||
public partial record TwitchClipEmbedProjection
|
||||
{
|
||||
private static string? TryParseClipId(string embedUrl)
|
||||
{
|
||||
// https://clips.twitch.tv/SpookyTenuousPidgeonPanicVis
|
||||
{
|
||||
var clipId = Regex
|
||||
.Match(embedUrl, @"clips\.twitch\.tv/(.*?)(?:\?|&|/|$)")
|
||||
.Groups[1]
|
||||
.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(clipId))
|
||||
return clipId;
|
||||
}
|
||||
|
||||
// https://twitch.tv/clip/SpookyTenuousPidgeonPanicVis
|
||||
{
|
||||
var clipId = Regex
|
||||
.Match(embedUrl, @"twitch\.tv/clip/(.*?)(?:\?|&|/|$)")
|
||||
.Groups[1]
|
||||
.Value;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(clipId))
|
||||
return clipId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TwitchClipEmbedProjection? TryResolve(Embed embed)
|
||||
{
|
||||
if (embed.Kind != EmbedKind.Video)
|
||||
return null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(embed.Url))
|
||||
return null;
|
||||
|
||||
var clipId = TryParseClipId(embed.Url);
|
||||
if (string.IsNullOrWhiteSpace(clipId))
|
||||
return null;
|
||||
|
||||
return new TwitchClipEmbedProjection(clipId);
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ public class ChannelExporter(DiscordClient discord)
|
||||
if (request.Channel.Kind == ChannelKind.GuildForum)
|
||||
{
|
||||
throw new DiscordChatExporterException(
|
||||
$"Channel '{request.Channel.Name}' (#{request.Channel.Id}) "
|
||||
+ $"of guild '{request.Guild.Name}' (#{request.Guild.Id}) "
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"is a forum and cannot be exported directly. "
|
||||
+ "You need to pull its threads and export them individually."
|
||||
);
|
||||
@@ -31,8 +31,8 @@ public class ChannelExporter(DiscordClient discord)
|
||||
if (request.Channel.IsEmpty)
|
||||
{
|
||||
throw new DiscordChatExporterException(
|
||||
$"Channel '{request.Channel.Name}' (#{request.Channel.Id}) "
|
||||
+ $"of guild '{request.Guild.Name}' (#{request.Guild.Id}) "
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages."
|
||||
);
|
||||
}
|
||||
@@ -41,8 +41,8 @@ public class ChannelExporter(DiscordClient discord)
|
||||
if (request.After is not null && !request.Channel.MayHaveMessagesAfter(request.After.Value))
|
||||
{
|
||||
throw new DiscordChatExporterException(
|
||||
$"Channel '{request.Channel.Name}' (#{request.Channel.Id}) "
|
||||
+ $"of guild '{request.Guild.Name}' (#{request.Guild.Id}) "
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages within the specified period."
|
||||
);
|
||||
}
|
||||
@@ -54,8 +54,8 @@ public class ChannelExporter(DiscordClient discord)
|
||||
)
|
||||
{
|
||||
throw new DiscordChatExporterException(
|
||||
$"Channel '{request.Channel.Name}' (#{request.Channel.Id}) "
|
||||
+ $"of guild '{request.Guild.Name}' (#{request.Guild.Id}) "
|
||||
$"Channel '{request.Channel.Name}' "
|
||||
+ $"of guild '{request.Guild.Name}' "
|
||||
+ $"does not contain any messages within the specified period."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -448,7 +448,10 @@
|
||||
</div>
|
||||
}
|
||||
// Generic video embed
|
||||
else if (embed.Kind == EmbedKind.Video && !string.IsNullOrWhiteSpace(embed.Url))
|
||||
else if (embed.Kind == EmbedKind.Video
|
||||
&& !string.IsNullOrWhiteSpace(embed.Url)
|
||||
// Twitch clips cannot be embedded in local HTML files
|
||||
&& embed.TryGetTwitchClip() is null)
|
||||
{
|
||||
var embedVideoUrl =
|
||||
embed.Video?.ProxyUrl ?? embed.Video?.Url ??
|
||||
|
||||
@@ -702,6 +702,10 @@
|
||||
.chatlog__embed-spotify {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.chatlog__embed-twitch {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.chatlog__embed-youtube-container {
|
||||
margin-top: 0.6rem;
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# If we are root, ensure the files in /out are writable
|
||||
# by the dce user and restart the process as the dce user
|
||||
if [ "$(id -u)" = '0' ]; then
|
||||
chown -R dce:dce /out
|
||||
exec su-exec dce "$0" "$@"
|
||||
fi
|
||||
|
||||
exec /opt/app/DiscordChatExporter.Cli "$@"
|
||||
Reference in New Issue
Block a user