mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-19 12:06:16 +02:00
Compare commits
18 Commits
2.47
...
5c61306231
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c61306231 | |||
| 2e4601d378 | |||
| cc703cc860 | |||
| 661f82c478 | |||
| b9f19f7003 | |||
| e794d7c833 | |||
| 5a26ba0b5a | |||
| 1181a35f08 | |||
| 4a62f9c34f | |||
| b6e1f92989 | |||
| 0d3d2ddcf0 | |||
| 9afecd47bd | |||
| 5cec162b4e | |||
| 6647f90ced | |||
| b9a3001c11 | |||
| dadfe5b8ef | |||
| 4f29fa63d0 | |||
| 54b0174bde |
+2
-2
@@ -17,7 +17,7 @@ Note the `:stable` tag. DiscordChatExporter images are tagged according to the f
|
||||
|
||||
- `stable` — latest stable version release. This tag is updated with each release of a new project version. Recommended for personal use.
|
||||
- `x.y.z` (e.g. `2.30.1`) — specific stable version release. This tag is pushed when the corresponding version is released and never updated thereafter. Recommended for use in automation scenarios.
|
||||
- `latest` — latest (potentially unstable) build. This tag is updated with each new commit to the `master` branch. Not recommended, unless you want to test a new feature that has not been released in a stable version yet.
|
||||
- `latest` — latest (potentially unstable) build. This tag is updated with each new commit to the `prime` branch. Not recommended, unless you want to test a new feature that has not been released in a stable version yet.
|
||||
|
||||
You can see all available tags [here](https://hub.docker.com/r/tyrrrz/discordchatexporter/tags?ordering=name).
|
||||
|
||||
@@ -57,7 +57,7 @@ You can also use the current working directory as the output directory by specif
|
||||
- `-v $PWD:/out` in Bash
|
||||
- `-v $pwd.Path:/out` in PowerShell
|
||||
|
||||
For more information, please refer to the [Dockerfile](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/DiscordChatExporter.Cli.dockerfile) and [Docker documentation](https://docs.docker.com/engine/reference/run).
|
||||
For more information, please refer to the [Dockerfile](https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/DiscordChatExporter.Cli.dockerfile) and [Docker documentation](https://docs.docker.com/engine/reference/run).
|
||||
|
||||
To get your Token and Channel IDs, please refer to [this page](Token-and-IDs.md).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Info: https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs
|
||||
# Info: https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs
|
||||
|
||||
TOKEN=tokenhere
|
||||
CHANNELID=channelhere
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Info: https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs
|
||||
# Info: https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs
|
||||
|
||||
TOKEN=tokenhere
|
||||
CHANNELID=channelhere
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
1. Open a text editor such as Notepad and paste:
|
||||
|
||||
```console
|
||||
# Info: https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs
|
||||
# Info: https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs
|
||||
|
||||
$TOKEN = "tokenhere"
|
||||
$CHANNEL = "channelhere"
|
||||
|
||||
@@ -27,7 +27,7 @@ Prerequisite step: Navigate to [discord.com](https://discord.com) and login.
|
||||
3. Type
|
||||
|
||||
```js
|
||||
let m;webpackChunkdiscord_app.push([[Math.random()],{},e=>{for(let i in e.c){let x=e.c[i];if(x?.exports?.$8&&x.exports.LP&&x.exports.gK){m=x;break}}}]);m&&console.log("Token:",m.exports.LP());
|
||||
let m;webpackChunkdiscord_app.push([[Math.random()],{},e=>{for(let i in e.c){let x=e.c[i];if(x?.exports?.getToken){m=x;break}}}]);m&&console.log("Token:",m.exports.getToken());
|
||||
```
|
||||
|
||||
into the console and press <kbd>Enter</kbd>. The console will display your user token.
|
||||
@@ -120,7 +120,7 @@ Prerequisite step: Navigate to [discord.com](https://discord.com) and login.
|
||||
1. Type
|
||||
|
||||
```js
|
||||
let m;webpackChunkdiscord_app.push([[Math.random()],{},e=>{for(let i in e.c){let x=e.c[i];if(x?.exports?.$8&&x.exports.LP&&x.exports.gK){m=x;break}}}]);m&&console.log("Token:",m.exports.LP());
|
||||
let m;webpackChunkdiscord_app.push([[Math.random()],{},e=>{for(let i in e.c){let x=e.c[i];if(x?.exports?.getToken){m=x;break}}}]);m&&console.log("Token:",m.exports.getToken());
|
||||
```
|
||||
|
||||
into the console and press <kbd>Enter</kbd>. The console will display your user token.
|
||||
|
||||
+11
-12
@@ -29,21 +29,20 @@ Type the following command in your terminal of choice, then press ENTER to run i
|
||||
> **Note**:
|
||||
> On Windows, if you're using the default Command Prompt (`cmd`), omit the leading `./` at the start of the command.
|
||||
|
||||
|
||||
> **Docker** users, please refer to the [Docker usage instructions](Docker.md).
|
||||
|
||||
## CLI commands
|
||||
|
||||
| Command | Description |
|
||||
|-------------------------|------------------------------------------------------|
|
||||
| export | Exports a channel |
|
||||
| exportdm | Exports all direct message channels |
|
||||
| exportguild | Exports all channels within the specified server |
|
||||
| exportall | Exports all accessible channels |
|
||||
| channels | Outputs the list of channels in the given server |
|
||||
| dm | Outputs the list of direct message channels |
|
||||
| guilds | Outputs the list of accessible servers |
|
||||
| guide | Explains how to obtain token, server, and channel ID |
|
||||
| Command | Description |
|
||||
| ----------- | ---------------------------------------------------- |
|
||||
| export | Exports a channel |
|
||||
| exportdm | Exports all direct message channels |
|
||||
| exportguild | Exports all channels within the specified server |
|
||||
| exportall | Exports all accessible channels |
|
||||
| channels | Outputs the list of channels in the given server |
|
||||
| dm | Outputs the list of direct message channels |
|
||||
| guilds | Outputs the list of accessible servers |
|
||||
| guide | Explains how to obtain token, server, and channel ID |
|
||||
|
||||
To use the commands, you'll need a token. For the instructions on how to get a token, please refer to [this page](Token-and-IDs.md), or run `./DiscordChatExporter.Cli guide`.
|
||||
|
||||
@@ -222,7 +221,7 @@ Use `--filter` to filter what messages are included in the export.
|
||||
./DiscordChatExporter.Cli export -t "mfa.Ifrn" -c 53555 --filter "from:Tyrrrz has:image"
|
||||
```
|
||||
|
||||
Documentation on message filter syntax can be found [here](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs/Message-filters.md).
|
||||
Documentation on message filter syntax can be found [here](https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs/Message-filters.md).
|
||||
|
||||
### Export channels from a specific server
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ⚠ Feature request
|
||||
url: https://github.com/Tyrrrz/.github/blob/master/docs/project-status.md
|
||||
url: https://github.com/Tyrrrz/.github/blob/prime/docs/project-status.md
|
||||
about: Sorry, but this project is in maintenance mode and no longer accepts new feature requests.
|
||||
- name: 📖 Documentation
|
||||
url: https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs
|
||||
url: https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs
|
||||
about: Find usage guides and frequently asked questions.
|
||||
- name: 🗨 Discussions
|
||||
url: https://github.com/Tyrrrz/DiscordChatExporter/discussions/new
|
||||
|
||||
@@ -4,12 +4,12 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- prime
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- prime
|
||||
|
||||
jobs:
|
||||
# Outputs from this job aren't really used, but it's here to verify that the Dockerfile builds correctly
|
||||
@@ -37,15 +37,15 @@ jobs:
|
||||
--output type=tar,dest=DiscordChatExporter.Cli.Docker.tar
|
||||
|
||||
- name: Upload image
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
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
|
||||
if: ${{ github.ref_type == 'tag' || github.ref_type == 'branch' && github.ref_name == 'master' }}
|
||||
# Deploy to DockerHub only on tag push or prime branch push
|
||||
if: ${{ github.ref_type == 'tag' || github.ref_type == 'branch' && github.ref_name == 'prime' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
@@ -4,12 +4,12 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- prime
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- prime
|
||||
|
||||
env:
|
||||
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
|
||||
@@ -135,7 +135,7 @@ jobs:
|
||||
--self-contained
|
||||
|
||||
- name: Upload app binaries
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ matrix.asset }}.${{ matrix.rid }}
|
||||
path: ${{ matrix.app }}/bin/publish/
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download app binaries
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
|
||||
with:
|
||||
name: ${{ matrix.asset }}.${{ matrix.rid }}
|
||||
path: ${{ matrix.app }}/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>999.9.9-dev</Version>
|
||||
@@ -7,8 +6,8 @@
|
||||
<Copyright>Copyright (c) Oleksii Holub</Copyright>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ILLinkTreatWarningsAsErrors>false</ILLinkTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
+19
-16
@@ -5,16 +5,16 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AngleSharp" Version="1.4.0" />
|
||||
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.5.0" />
|
||||
<PackageVersion Include="AsyncKeyedLock" Version="8.0.1" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.11" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.11" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.11" />
|
||||
<PackageVersion Include="CliFx" Version="2.3.6" />
|
||||
<PackageVersion Include="AsyncImageLoader.Avalonia" Version="3.6.0" />
|
||||
<PackageVersion Include="AsyncKeyedLock" Version="8.0.2" />
|
||||
<PackageVersion Include="Avalonia" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
|
||||
<PackageVersion Include="CliFx" Version="3.0.0-alpha.2" />
|
||||
<PackageVersion Include="Cogwheel" Version="2.1.0" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="CSharpier.MsBuild" Version="1.2.5" />
|
||||
<PackageVersion Include="coverlet.collector" Version="8.0.0" />
|
||||
<PackageVersion Include="CSharpier.MsBuild" Version="1.2.6" />
|
||||
<PackageVersion Include="Deorcify" Version="1.1.0" />
|
||||
<PackageVersion Include="ThisAssembly.Project" Version="2.1.2" />
|
||||
<PackageVersion Include="DialogHost.Avalonia" Version="0.10.4" />
|
||||
@@ -22,22 +22,25 @@
|
||||
<PackageVersion Include="GitHubActionsTestLogger" Version="3.0.1" />
|
||||
<PackageVersion Include="Gress" Version="2.1.1" />
|
||||
<PackageVersion Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageVersion Include="Markdig" Version="1.0.0" />
|
||||
<PackageVersion Include="Markdig" Version="1.0.1" />
|
||||
<PackageVersion Include="Material.Avalonia" Version="3.9.2" />
|
||||
<PackageVersion Include="Material.Icons.Avalonia" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.3" />
|
||||
<PackageVersion
|
||||
Include="Microsoft.Extensions.Configuration.EnvironmentVariables"
|
||||
Version="10.0.3"
|
||||
/>
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageVersion Include="Onova" Version="2.6.13" />
|
||||
<PackageVersion Include="Polly" Version="8.6.5" />
|
||||
<PackageVersion Include="RazorBlade" Version="0.11.0" />
|
||||
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
|
||||
<PackageVersion Include="Superpower" Version="3.1.0" />
|
||||
<PackageVersion Include="WebMarkupMin.Core" Version="2.20.1" />
|
||||
<PackageVersion Include="WebMarkupMin.Core" Version="2.20.2" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
|
||||
<PackageVersion Include="YoutubeExplode" Version="6.5.6" />
|
||||
<PackageVersion Include="YoutubeExplode" Version="6.5.7" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Core.Discord;
|
||||
using DiscordChatExporter.Core.Utils;
|
||||
@@ -17,23 +17,22 @@ public abstract class DiscordCommandBase : ICommand
|
||||
EnvironmentVariable = "DISCORD_TOKEN",
|
||||
Description = "Authentication token."
|
||||
)]
|
||||
public required string Token { get; init; }
|
||||
public required string Token { get; set; }
|
||||
|
||||
[Obsolete("This option doesn't do anything. Kept for backwards compatibility.")]
|
||||
[CommandOption(
|
||||
"bot",
|
||||
'b',
|
||||
EnvironmentVariable = "DISCORD_TOKEN_BOT",
|
||||
Description = "This option doesn't do anything. Kept for backwards compatibility."
|
||||
)]
|
||||
public bool IsBotToken { get; init; } = false;
|
||||
public bool IsBotToken { get; set; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"respect-rate-limits",
|
||||
Description = "Whether to respect advisory rate limits. "
|
||||
+ "If disabled, only hard rate limits (i.e. 429 responses) will be respected."
|
||||
)]
|
||||
public bool ShouldRespectRateLimits { get; init; } = true;
|
||||
public bool ShouldRespectRateLimits { get; set; } = true;
|
||||
|
||||
[field: AllowNull, MaybeNull]
|
||||
protected DiscordClient Discord =>
|
||||
|
||||
@@ -5,8 +5,8 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Exceptions;
|
||||
using CliFx;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Converters;
|
||||
using DiscordChatExporter.Cli.Commands.Shared;
|
||||
@@ -37,23 +37,23 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
get;
|
||||
// Handle ~/ in paths on Unix systems
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/pull/903
|
||||
init => field = Path.GetFullPath(value);
|
||||
set => field = Path.GetFullPath(value);
|
||||
} = Directory.GetCurrentDirectory();
|
||||
|
||||
[CommandOption("format", 'f', Description = "Export format.")]
|
||||
public ExportFormat ExportFormat { get; init; } = ExportFormat.HtmlDark;
|
||||
public ExportFormat ExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||
|
||||
[CommandOption(
|
||||
"after",
|
||||
Description = "Only include messages sent after this date or message ID."
|
||||
)]
|
||||
public Snowflake? After { get; init; }
|
||||
public Snowflake? After { get; set; }
|
||||
|
||||
[CommandOption(
|
||||
"before",
|
||||
Description = "Only include messages sent before this date or message ID."
|
||||
)]
|
||||
public Snowflake? Before { get; init; }
|
||||
public Snowflake? Before { get; set; }
|
||||
|
||||
[CommandOption(
|
||||
"partition",
|
||||
@@ -61,51 +61,51 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
Description = "Split the output into partitions, each limited to the specified "
|
||||
+ "number of messages (e.g. '100') or file size (e.g. '10mb')."
|
||||
)]
|
||||
public PartitionLimit PartitionLimit { get; init; } = PartitionLimit.Null;
|
||||
public PartitionLimit PartitionLimit { get; set; } = PartitionLimit.Null;
|
||||
|
||||
[CommandOption(
|
||||
"include-threads",
|
||||
Description = "Which types of threads should be included.",
|
||||
Converter = typeof(ThreadInclusionModeBindingConverter)
|
||||
Converter = typeof(ThreadInclusionModeInputConverter)
|
||||
)]
|
||||
public ThreadInclusionMode ThreadInclusionMode { get; init; } = ThreadInclusionMode.None;
|
||||
public ThreadInclusionMode ThreadInclusionMode { get; set; } = ThreadInclusionMode.None;
|
||||
|
||||
[CommandOption(
|
||||
"filter",
|
||||
Description = "Only include messages that satisfy this filter. "
|
||||
+ "See the documentation for more info."
|
||||
)]
|
||||
public MessageFilter MessageFilter { get; init; } = MessageFilter.Null;
|
||||
public MessageFilter MessageFilter { get; set; } = MessageFilter.Null;
|
||||
|
||||
[CommandOption(
|
||||
"parallel",
|
||||
Description = "Limits how many channels can be exported in parallel."
|
||||
)]
|
||||
public int ParallelLimit { get; init; } = 1;
|
||||
public int ParallelLimit { get; set; } = 1;
|
||||
|
||||
[CommandOption(
|
||||
"reverse",
|
||||
Description = "Export messages in reverse chronological order (newest first)."
|
||||
)]
|
||||
public bool IsReverseMessageOrder { get; init; }
|
||||
public bool IsReverseMessageOrder { get; set; }
|
||||
|
||||
[CommandOption(
|
||||
"markdown",
|
||||
Description = "Process markdown, mentions, and other special tokens."
|
||||
)]
|
||||
public bool ShouldFormatMarkdown { get; init; } = true;
|
||||
public bool ShouldFormatMarkdown { get; set; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"media",
|
||||
Description = "Download assets referenced by the export (user avatars, attached files, embedded images, etc.)."
|
||||
)]
|
||||
public bool ShouldDownloadAssets { get; init; }
|
||||
public bool ShouldDownloadAssets { get; set; }
|
||||
|
||||
[CommandOption(
|
||||
"reuse-media",
|
||||
Description = "Reuse previously downloaded assets to avoid redundant requests."
|
||||
)]
|
||||
public bool ShouldReuseAssets { get; init; } = false;
|
||||
public bool ShouldReuseAssets { get; set; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"media-dir",
|
||||
@@ -117,34 +117,33 @@ public abstract class ExportCommandBase : DiscordCommandBase
|
||||
get;
|
||||
// Handle ~/ in paths on Unix systems
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/pull/903
|
||||
init => field = value is not null ? Path.GetFullPath(value) : null;
|
||||
set => field = value is not null ? Path.GetFullPath(value) : null;
|
||||
}
|
||||
|
||||
[Obsolete("This option doesn't do anything. Kept for backwards compatibility.")]
|
||||
[CommandOption(
|
||||
"dateformat",
|
||||
Description = "This option doesn't do anything. Kept for backwards compatibility."
|
||||
)]
|
||||
public string DateFormat { get; init; } = "MM/dd/yyyy h:mm tt";
|
||||
public string DateFormat { get; set; } = "MM/dd/yyyy h:mm tt";
|
||||
|
||||
[CommandOption(
|
||||
"locale",
|
||||
Description = "Locale to use when formatting dates and numbers. "
|
||||
+ "If not specified, the default system locale will be used."
|
||||
)]
|
||||
public string? Locale { get; init; }
|
||||
public string? Locale { get; set; }
|
||||
|
||||
[CommandOption("utc", Description = "Normalize all timestamps to UTC+0.")]
|
||||
public bool IsUtcNormalizationEnabled { get; init; } = false;
|
||||
public bool IsUtcNormalizationEnabled { get; set; } = false;
|
||||
|
||||
[CommandOption(
|
||||
"fuck-russia",
|
||||
EnvironmentVariable = "FUCK_RUSSIA",
|
||||
Description = "Don't print the Support Ukraine message to the console.",
|
||||
// Use a converter to accept '1' as 'true' to reuse the existing environment variable
|
||||
Converter = typeof(TruthyBooleanBindingConverter)
|
||||
Converter = typeof(TruthyBooleanInputConverter)
|
||||
)]
|
||||
public bool IsUkraineSupportMessageDisabled { get; init; } = false;
|
||||
public bool IsUkraineSupportMessageDisabled { get; set; } = false;
|
||||
|
||||
[field: AllowNull, MaybeNull]
|
||||
protected ChannelExporter Exporter => field ??= new ChannelExporter(Discord);
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using CliFx.Extensibility;
|
||||
using CliFx.Activation;
|
||||
using DiscordChatExporter.Cli.Commands.Shared;
|
||||
|
||||
namespace DiscordChatExporter.Cli.Commands.Converters;
|
||||
|
||||
internal class ThreadInclusionModeBindingConverter : BindingConverter<ThreadInclusionMode>
|
||||
internal class ThreadInclusionModeInputConverter : ScalarInputConverter<ThreadInclusionMode>
|
||||
{
|
||||
public override ThreadInclusionMode Convert(string? rawValue)
|
||||
{
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Globalization;
|
||||
using CliFx.Extensibility;
|
||||
using CliFx.Activation;
|
||||
|
||||
namespace DiscordChatExporter.Cli.Commands.Converters;
|
||||
|
||||
internal class TruthyBooleanBindingConverter : BindingConverter<bool>
|
||||
internal class TruthyBooleanInputConverter : ScalarInputConverter<bool>
|
||||
{
|
||||
public override bool Convert(string? rawValue)
|
||||
{
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Cli.Utils.Extensions;
|
||||
@@ -14,23 +14,23 @@ using Spectre.Console;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("exportall", Description = "Exports all accessible channels.")]
|
||||
public class ExportAllCommand : ExportCommandBase
|
||||
public partial class ExportAllCommand : ExportCommandBase
|
||||
{
|
||||
[CommandOption("include-dm", Description = "Include direct message channels.")]
|
||||
public bool IncludeDirectChannels { get; init; } = true;
|
||||
public bool IncludeDirectChannels { get; set; } = true;
|
||||
|
||||
[CommandOption("include-guilds", Description = "Include server channels.")]
|
||||
public bool IncludeGuildChannels { get; init; } = true;
|
||||
public bool IncludeGuildChannels { get; set; } = true;
|
||||
|
||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
||||
public bool IncludeVoiceChannels { get; init; } = true;
|
||||
public bool IncludeVoiceChannels { get; set; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"data-package",
|
||||
Description = "Path to the personal data package (ZIP file) requested from Discord. "
|
||||
+ "If provided, only channels referenced in the dump will be exported."
|
||||
)]
|
||||
public string? DataPackageFilePath { get; init; }
|
||||
public string? DataPackageFilePath { get; set; }
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Core.Discord;
|
||||
@@ -10,7 +10,7 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("export", Description = "Exports one or multiple channels.")]
|
||||
public class ExportChannelsCommand : ExportCommandBase
|
||||
public partial class ExportChannelsCommand : ExportCommandBase
|
||||
{
|
||||
// TODO: change this to plural (breaking change)
|
||||
[CommandOption(
|
||||
@@ -19,7 +19,7 @@ public class ExportChannelsCommand : ExportCommandBase
|
||||
Description = "Channel ID(s). "
|
||||
+ "If provided with category ID(s), all channels inside those categories will be exported."
|
||||
)]
|
||||
public required IReadOnlyList<Snowflake> ChannelIds { get; init; }
|
||||
public required IReadOnlyList<Snowflake> ChannelIds { get; set; }
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Core.Discord.Data;
|
||||
@@ -8,7 +8,7 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("exportdm", Description = "Exports all direct message channels.")]
|
||||
public class ExportDirectMessagesCommand : ExportCommandBase
|
||||
public partial class ExportDirectMessagesCommand : ExportCommandBase
|
||||
{
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Cli.Utils.Extensions;
|
||||
@@ -11,13 +11,13 @@ using Spectre.Console;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("exportguild", Description = "Exports all channels within the specified server.")]
|
||||
public class ExportGuildCommand : ExportCommandBase
|
||||
public partial class ExportGuildCommand : ExportCommandBase
|
||||
{
|
||||
[CommandOption("guild", 'g', Description = "Server ID.")]
|
||||
public required Snowflake GuildId { get; init; }
|
||||
public required Snowflake GuildId { get; set; }
|
||||
|
||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
||||
public bool IncludeVoiceChannels { get; init; } = true;
|
||||
public bool IncludeVoiceChannels { get; set; } = true;
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Cli.Commands.Converters;
|
||||
@@ -12,20 +12,20 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("channels", Description = "Get the list of channels in a server.")]
|
||||
public class GetChannelsCommand : DiscordCommandBase
|
||||
public partial class GetChannelsCommand : DiscordCommandBase
|
||||
{
|
||||
[CommandOption("guild", 'g', Description = "Server ID.")]
|
||||
public required Snowflake GuildId { get; init; }
|
||||
public required Snowflake GuildId { get; set; }
|
||||
|
||||
[CommandOption("include-vc", Description = "Include voice channels.")]
|
||||
public bool IncludeVoiceChannels { get; init; } = true;
|
||||
public bool IncludeVoiceChannels { get; set; } = true;
|
||||
|
||||
[CommandOption(
|
||||
"include-threads",
|
||||
Description = "Which types of threads should be included.",
|
||||
Converter = typeof(ThreadInclusionModeBindingConverter)
|
||||
Converter = typeof(ThreadInclusionModeInputConverter)
|
||||
)]
|
||||
public ThreadInclusionMode ThreadInclusionMode { get; init; } = ThreadInclusionMode.None;
|
||||
public ThreadInclusionMode ThreadInclusionMode { get; set; } = ThreadInclusionMode.None;
|
||||
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Core.Discord.Data;
|
||||
@@ -10,7 +10,7 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("dm", Description = "Gets the list of all direct message channels.")]
|
||||
public class GetDirectChannelsCommand : DiscordCommandBase
|
||||
public partial class GetDirectChannelsCommand : DiscordCommandBase
|
||||
{
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
using DiscordChatExporter.Cli.Commands.Base;
|
||||
using DiscordChatExporter.Core.Discord.Data;
|
||||
@@ -10,7 +10,7 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("guilds", Description = "Gets the list of accessible servers.")]
|
||||
public class GetGuildsCommand : DiscordCommandBase
|
||||
public partial class GetGuildsCommand : DiscordCommandBase
|
||||
{
|
||||
public override async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using CliFx.Attributes;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
|
||||
namespace DiscordChatExporter.Cli.Commands;
|
||||
|
||||
[Command("guide", Description = "Explains how to obtain the token, server or channel ID.")]
|
||||
public class GuideCommand : ICommand
|
||||
public partial class GuideCommand : ICommand
|
||||
{
|
||||
public ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
@@ -74,7 +74,7 @@ public class GuideCommand : ICommand
|
||||
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
||||
{
|
||||
console.Output.WriteLine(
|
||||
"https://github.com/Tyrrrz/DiscordChatExporter/blob/master/.docs"
|
||||
"https://github.com/Tyrrrz/DiscordChatExporter/blob/prime/.docs"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
<CopyOutputSymbolsToPublishDirectory>false</CopyOutputSymbolsToPublishDirectory>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- HACK: Disable trim warnings because they seem to break when the code contains C# 14 extension blocks -->
|
||||
<PropertyGroup>
|
||||
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
|
||||
<EnableAotAnalyzer>false</EnableAotAnalyzer>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliFx" />
|
||||
<PackageReference Include="CSharpier.MsBuild" PrivateAssets="all" />
|
||||
|
||||
@@ -1,41 +1,13 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using DiscordChatExporter.Cli.Commands;
|
||||
using DiscordChatExporter.Cli.Commands.Converters;
|
||||
using DiscordChatExporter.Core.Exporting.Filtering;
|
||||
using DiscordChatExporter.Core.Exporting.Partitioning;
|
||||
|
||||
namespace DiscordChatExporter.Cli;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
// Explicit references because CliFx relies on reflection and we're publishing with trimming enabled
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportAllCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportChannelsCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportDirectMessagesCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(ExportGuildCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GetChannelsCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GetDirectChannelsCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GetGuildsCommand))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GuideCommand))]
|
||||
[DynamicDependency(
|
||||
DynamicallyAccessedMemberTypes.All,
|
||||
typeof(ThreadInclusionModeBindingConverter)
|
||||
)]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TruthyBooleanBindingConverter))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(PartitionLimit))]
|
||||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(MessageFilter))]
|
||||
public static async Task<int> Main(string[] args) =>
|
||||
await new CliApplicationBuilder()
|
||||
.AddCommand<ExportAllCommand>()
|
||||
.AddCommand<ExportChannelsCommand>()
|
||||
.AddCommand<ExportDirectMessagesCommand>()
|
||||
.AddCommand<ExportGuildCommand>()
|
||||
.AddCommand<GetChannelsCommand>()
|
||||
.AddCommand<GetDirectChannelsCommand>()
|
||||
.AddCommand<GetGuildsCommand>()
|
||||
.AddCommand<GuideCommand>()
|
||||
await new CommandLineApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.Build()
|
||||
.RunAsync(args);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ public class App : Application, IDisposable
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly MainViewModel _mainViewModel;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
public App()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
@@ -98,8 +100,24 @@ public class App : Application, IDisposable
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainView { DataContext = _mainViewModel };
|
||||
|
||||
void OnExit(object? sender, ControlledApplicationLifetimeExitEventArgs args)
|
||||
{
|
||||
if (sender is IControlledApplicationLifetime lifetime)
|
||||
lifetime.Exit -= OnExit;
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
// Although `App.Dispose()` is invoked from `Program.Main(...)`, on some platforms
|
||||
// it may be called too late in the shutdown lifecycle. Attach an exit
|
||||
// handler to ensure timely disposal as a safeguard.
|
||||
// https://github.com/Tyrrrz/YoutubeDownloader/issues/795
|
||||
desktop.Exit += OnExit;
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
// Set up custom theme colors
|
||||
@@ -115,6 +133,11 @@ public class App : Application, IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_isDisposed)
|
||||
return;
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
_eventRoot.Dispose();
|
||||
_services.Dispose();
|
||||
}
|
||||
|
||||
@@ -21,12 +21,6 @@
|
||||
<PublishMacOSBundle>false</PublishMacOSBundle>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- HACK: Disable trim warnings because they seem to break when the code contains C# 14 extension blocks -->
|
||||
<PropertyGroup>
|
||||
<EnableTrimAnalyzer>false</EnableTrimAnalyzer>
|
||||
<EnableAotAnalyzer>false</EnableAotAnalyzer>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="..\favicon.ico" Link="favicon.ico" />
|
||||
</ItemGroup>
|
||||
@@ -56,7 +50,7 @@
|
||||
|
||||
<Target Name="PublishMacOSBundle" AfterTargets="Publish" Condition="$(PublishMacOSBundle)">
|
||||
<Exec
|
||||
Command="pwsh -ExecutionPolicy Bypass -File $(ProjectDir)/Publish-MacOSBundle.ps1 -PublishDirPath $(PublishDir) -IconsFilePath $(ProjectDir)/../favicon.icns -FullVersion $(Version) -ShortVersion $(AssemblyVersion)"
|
||||
Command="dotnet run --file "$(ProjectDir)PublishMacOSBundle.csx" -- --publish-dir "$(PublishDir)" --icons-file "$(ProjectDir)../favicon.icns" --full-version $(Version) --short-version $(AssemblyVersion)"
|
||||
LogStandardErrorAsError="true"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
@@ -42,7 +42,7 @@ public partial class LocalizationManager
|
||||
* Your bot needs to have the **Message Content Intent** enabled to read messages
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"If you have questions or issues, please refer to the [documentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
"If you have questions or issues, please refer to the [documentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/prime/.docs)",
|
||||
// Settings
|
||||
[nameof(SettingsTitle)] = "Settings",
|
||||
[nameof(ThemeLabel)] = "Theme",
|
||||
|
||||
@@ -44,7 +44,7 @@ public partial class LocalizationManager
|
||||
* Votre bot doit avoir l'option **Message Content Intent** activée pour lire les messages
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Pour les questions ou problèmes, veuillez consulter la [documentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
"Pour les questions ou problèmes, veuillez consulter la [documentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/prime/.docs)",
|
||||
// Settings
|
||||
[nameof(SettingsTitle)] = "Paramètres",
|
||||
[nameof(ThemeLabel)] = "Thème",
|
||||
|
||||
@@ -44,7 +44,7 @@ public partial class LocalizationManager
|
||||
* Ihr Bot benötigt die aktivierte **Message Content Intent**, um Nachrichten zu lesen
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Bei Fragen oder Problemen lesen Sie die [Dokumentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
"Bei Fragen oder Problemen lesen Sie die [Dokumentation](https://github.com/Tyrrrz/DiscordChatExporter/tree/prime/.docs)",
|
||||
// Settings
|
||||
[nameof(SettingsTitle)] = "Einstellungen",
|
||||
[nameof(ThemeLabel)] = "Design",
|
||||
|
||||
@@ -42,7 +42,7 @@ public partial class LocalizationManager
|
||||
* Tu bot necesita tener habilitado **Message Content Intent** para leer mensajes
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Si tienes preguntas o problemas, consulta la [documentación](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
"Si tienes preguntas o problemas, consulta la [documentación](https://github.com/Tyrrrz/DiscordChatExporter/tree/prime/.docs)",
|
||||
// Settings
|
||||
[nameof(SettingsTitle)] = "Ajustes",
|
||||
[nameof(ThemeLabel)] = "Tema",
|
||||
|
||||
@@ -42,7 +42,7 @@ public partial class LocalizationManager
|
||||
* Ваш бот повинен мати включений **Message Content Intent** для читання повідомлень
|
||||
""",
|
||||
[nameof(TokenHelpText)] =
|
||||
"Якщо у вас є запитання або проблеми, зверніться до [документації](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)",
|
||||
"Якщо у вас є запитання або проблеми, зверніться до [документації](https://github.com/Tyrrrz/DiscordChatExporter/tree/prime/.docs)",
|
||||
// Settings
|
||||
[nameof(SettingsTitle)] = "Налаштування",
|
||||
[nameof(ThemeLabel)] = "Тема",
|
||||
|
||||
@@ -21,7 +21,7 @@ public static class Program
|
||||
|
||||
public static string ProjectReleasesUrl { get; } = $"{ProjectUrl}/releases";
|
||||
|
||||
public static string ProjectDocumentationUrl { get; } = ProjectUrl + "/tree/master/.docs";
|
||||
public static string ProjectDocumentationUrl { get; } = ProjectUrl + "/tree/prime/.docs";
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp() =>
|
||||
AppBuilder.Configure<App>().UsePlatformDetect().LogToTrace();
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$PublishDirPath,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$IconsFilePath,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$FullVersion,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$ShortVersion
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Setup paths
|
||||
$tempDirPath = Join-Path $PublishDirPath "../publish-macos-app-temp"
|
||||
$bundleName = "DiscordChatExporter.app"
|
||||
$bundleDirPath = Join-Path $tempDirPath $bundleName
|
||||
$contentsDirPath = Join-Path $bundleDirPath "Contents"
|
||||
$macosDirPath = Join-Path $contentsDirPath "MacOS"
|
||||
$resourcesDirPath = Join-Path $contentsDirPath "Resources"
|
||||
|
||||
try {
|
||||
# Initialize the bundle's directory structure
|
||||
New-Item -Path $bundleDirPath -ItemType Directory -Force
|
||||
New-Item -Path $contentsDirPath -ItemType Directory -Force
|
||||
New-Item -Path $macosDirPath -ItemType Directory -Force
|
||||
New-Item -Path $resourcesDirPath -ItemType Directory -Force
|
||||
|
||||
# Copy icons into the .app's Resources folder
|
||||
Copy-Item -Path $IconsFilePath -Destination (Join-Path $resourcesDirPath "AppIcon.icns") -Force
|
||||
|
||||
# Generate the Info.plist metadata file with the app information
|
||||
$plistContent = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>DiscordChatExporter</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>DiscordChatExporter</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>DiscordChatExporter</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>© Oleksii Holub</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>me.Tyrrrz.DiscordChatExporter</string>
|
||||
<key>CFBundleSpokenName</key>
|
||||
<string>Discord Chat Exporter</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>AppIcon</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$FullVersion</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$ShortVersion</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true />
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
</dict>
|
||||
</plist>
|
||||
"@
|
||||
|
||||
Set-Content -Path (Join-Path $contentsDirPath "Info.plist") -Value $plistContent
|
||||
|
||||
# Delete the previous bundle if it exists
|
||||
if (Test-Path (Join-Path $PublishDirPath $bundleName)) {
|
||||
Remove-Item -Path (Join-Path $PublishDirPath $bundleName) -Recurse -Force
|
||||
}
|
||||
|
||||
# Move all files from the publish directory into the MacOS directory
|
||||
Get-ChildItem -Path $PublishDirPath | ForEach-Object {
|
||||
Move-Item -Path $_.FullName -Destination $macosDirPath -Force
|
||||
}
|
||||
|
||||
# Move the final bundle into the publish directory for upload
|
||||
Move-Item -Path $bundleDirPath -Destination $PublishDirPath -Force
|
||||
}
|
||||
finally {
|
||||
# Clean up the temporary directory
|
||||
Remove-Item -Path $tempDirPath -Recurse -Force
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/dotnet --
|
||||
#:package CliFx
|
||||
|
||||
using CliFx;
|
||||
using CliFx.Binding;
|
||||
using CliFx.Infrastructure;
|
||||
|
||||
return await new CommandLineApplicationBuilder()
|
||||
.AddCommandsFromThisAssembly()
|
||||
.Build()
|
||||
.RunAsync(args);
|
||||
|
||||
[Command(Description = "Publishes the GUI app as a macOS .app bundle.")]
|
||||
public partial class PublishMacOSBundleCommand : ICommand
|
||||
{
|
||||
private const string BundleName = "DiscordChatExporter.app";
|
||||
private const string AppName = "DiscordChatExporter";
|
||||
private const string AppCopyright = "© Oleksii Holub";
|
||||
private const string AppIdentifier = "me.Tyrrrz.DiscordChatExporter";
|
||||
private const string AppSpokenName = "Discord Chat Exporter";
|
||||
private const string AppIconName = "AppIcon";
|
||||
|
||||
[CommandOption("publish-dir", Description = "Path to the publish output directory.")]
|
||||
public required string PublishDirPath { get; set; }
|
||||
|
||||
[CommandOption("icons-file", Description = "Path to the .icns icons file.")]
|
||||
public required string IconsFilePath { get; set; }
|
||||
|
||||
[CommandOption("full-version", Description = "Full version string (e.g. '1.2.3.4').")]
|
||||
public required string FullVersion { get; set; }
|
||||
|
||||
[CommandOption("short-version", Description = "Short version string (e.g. '1.2.3').")]
|
||||
public required string ShortVersion { get; set; }
|
||||
|
||||
public async ValueTask ExecuteAsync(IConsole console)
|
||||
{
|
||||
// Set up paths
|
||||
var publishDirPath = Path.GetFullPath(PublishDirPath);
|
||||
var tempDirPath = Path.GetFullPath(
|
||||
Path.Combine(publishDirPath, "../publish-macos-app-temp")
|
||||
);
|
||||
|
||||
// Ensure the temporary directory is clean before use in case a previous run crashed
|
||||
if (Directory.Exists(tempDirPath))
|
||||
Directory.Delete(tempDirPath, true);
|
||||
|
||||
var bundleDirPath = Path.Combine(tempDirPath, BundleName);
|
||||
var contentsDirPath = Path.Combine(bundleDirPath, "Contents");
|
||||
|
||||
try
|
||||
{
|
||||
// Copy icons into the .app's Resources folder
|
||||
Directory.CreateDirectory(Path.Combine(contentsDirPath, "Resources"));
|
||||
File.Copy(
|
||||
IconsFilePath,
|
||||
Path.Combine(contentsDirPath, "Resources", "AppIcon.icns"),
|
||||
true
|
||||
);
|
||||
|
||||
// Generate the Info.plist metadata file with the app information
|
||||
// lang=xml
|
||||
var plistContent = $"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>{AppName}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>{AppName}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>{AppName}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>{AppCopyright}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{AppIdentifier}</string>
|
||||
<key>CFBundleSpokenName</key>
|
||||
<string>{AppSpokenName}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>{AppIconName}</string>
|
||||
<key>CFBundleIconName</key>
|
||||
<string>{AppIconName}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>{FullVersion}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{ShortVersion}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true />
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
</dict>
|
||||
</plist>
|
||||
""";
|
||||
|
||||
await File.WriteAllTextAsync(Path.Combine(contentsDirPath, "Info.plist"), plistContent);
|
||||
|
||||
// Delete the previous bundle if it exists
|
||||
var existingBundlePath = Path.Combine(publishDirPath, BundleName);
|
||||
if (Directory.Exists(existingBundlePath))
|
||||
Directory.Delete(existingBundlePath, true);
|
||||
|
||||
// Move all files from the publish directory into the MacOS directory
|
||||
Directory.CreateDirectory(Path.Combine(contentsDirPath, "MacOS"));
|
||||
foreach (var entryPath in Directory.GetFileSystemEntries(publishDirPath))
|
||||
{
|
||||
var destinationPath = Path.Combine(
|
||||
contentsDirPath,
|
||||
"MacOS",
|
||||
Path.GetFileName(entryPath)
|
||||
);
|
||||
|
||||
if (Directory.Exists(entryPath))
|
||||
Directory.Move(entryPath, destinationPath);
|
||||
else
|
||||
File.Move(entryPath, destinationPath);
|
||||
}
|
||||
|
||||
// Move the final bundle into the publish directory for upload
|
||||
Directory.Move(bundleDirPath, Path.Combine(publishDirPath, BundleName));
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Clean up the temporary directory
|
||||
if (Directory.Exists(tempDirPath))
|
||||
Directory.Delete(tempDirPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization;
|
||||
using Cogwheel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using DiscordChatExporter.Core.Discord;
|
||||
@@ -13,10 +11,7 @@ namespace DiscordChatExporter.Gui.Services;
|
||||
|
||||
[ObservableObject]
|
||||
public partial class SettingsService()
|
||||
: SettingsBase(
|
||||
Path.Combine(AppContext.BaseDirectory, "Settings.dat"),
|
||||
SerializerContext.Default
|
||||
)
|
||||
: SettingsBase(StartOptions.Current.SettingsPath, SerializerContext.Default)
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial bool IsUkraineSupportMessageEnabled { get; set; } = true;
|
||||
|
||||
@@ -9,24 +9,25 @@ namespace DiscordChatExporter.Gui.Services;
|
||||
|
||||
public class UpdateService(SettingsService settingsService) : IDisposable
|
||||
{
|
||||
private readonly IUpdateManager? _updateManager = OperatingSystem.IsWindows()
|
||||
? new UpdateManager(
|
||||
new GithubPackageResolver(
|
||||
"Tyrrrz",
|
||||
"DiscordChatExporter",
|
||||
// Examples:
|
||||
// DiscordChatExporter.win-arm64.zip
|
||||
// DiscordChatExporter.win-x64.zip
|
||||
// DiscordChatExporter.linux-x64.zip
|
||||
$"DiscordChatExporter.{RuntimeInformation.RuntimeIdentifier}.zip"
|
||||
),
|
||||
new ZipPackageExtractor()
|
||||
)
|
||||
: null;
|
||||
private readonly IUpdateManager? _updateManager =
|
||||
OperatingSystem.IsWindows() && StartOptions.Current.IsAutoUpdateAllowed
|
||||
? new UpdateManager(
|
||||
new GithubPackageResolver(
|
||||
"Tyrrrz",
|
||||
"DiscordChatExporter",
|
||||
// Examples:
|
||||
// DiscordChatExporter.win-arm64.zip
|
||||
// DiscordChatExporter.win-x64.zip
|
||||
// DiscordChatExporter.linux-x64.zip
|
||||
$"DiscordChatExporter.{RuntimeInformation.RuntimeIdentifier}.zip"
|
||||
),
|
||||
new ZipPackageExtractor()
|
||||
)
|
||||
: null;
|
||||
|
||||
private Version? _updateVersion;
|
||||
private bool _updatePrepared;
|
||||
private bool _updaterLaunched;
|
||||
private bool _isUpdatePrepared;
|
||||
private bool _isUpdaterLaunched;
|
||||
|
||||
public async ValueTask<Version?> CheckForUpdatesAsync()
|
||||
{
|
||||
@@ -51,7 +52,7 @@ public class UpdateService(SettingsService settingsService) : IDisposable
|
||||
try
|
||||
{
|
||||
await _updateManager.PrepareUpdateAsync(_updateVersion = version);
|
||||
_updatePrepared = true;
|
||||
_isUpdatePrepared = true;
|
||||
}
|
||||
catch (UpdaterAlreadyLaunchedException)
|
||||
{
|
||||
@@ -71,13 +72,13 @@ public class UpdateService(SettingsService settingsService) : IDisposable
|
||||
if (!settingsService.IsAutoUpdateEnabled)
|
||||
return;
|
||||
|
||||
if (_updateVersion is null || !_updatePrepared || _updaterLaunched)
|
||||
if (_updateVersion is null || !_isUpdatePrepared || _isUpdaterLaunched)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_updateManager.LaunchUpdater(_updateVersion, needRestart);
|
||||
_updaterLaunched = true;
|
||||
_isUpdaterLaunched = true;
|
||||
}
|
||||
catch (UpdaterAlreadyLaunchedException)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace DiscordChatExporter.Gui;
|
||||
|
||||
public partial class StartOptions
|
||||
{
|
||||
public required string SettingsPath { get; init; }
|
||||
|
||||
public required bool IsAutoUpdateAllowed { get; init; }
|
||||
}
|
||||
|
||||
public partial class StartOptions
|
||||
{
|
||||
public static StartOptions Current { get; } =
|
||||
new()
|
||||
{
|
||||
SettingsPath =
|
||||
Environment.GetEnvironmentVariable("DISCORDCHATEXPORTER_SETTINGS_PATH") is { } path
|
||||
&& !string.IsNullOrWhiteSpace(path)
|
||||
? Path.EndsInDirectorySeparator(path) || Directory.Exists(path)
|
||||
? Path.Combine(path, "Settings.dat")
|
||||
: path
|
||||
: Path.Combine(AppContext.BaseDirectory, "Settings.dat"),
|
||||
IsAutoUpdateAllowed = !(
|
||||
Environment.GetEnvironmentVariable("DISCORDCHATEXPORTER_ALLOW_AUTO_UPDATE")
|
||||
is { } env
|
||||
&& env.Equals("false", StringComparison.OrdinalIgnoreCase)
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -46,6 +46,9 @@ public class SettingsViewModel : DialogViewModelBase
|
||||
set => _settingsService.Language = value;
|
||||
}
|
||||
|
||||
public bool IsAutoUpdateAvailable { get; } =
|
||||
OperatingSystem.IsWindows() && StartOptions.Current.IsAutoUpdateAllowed;
|
||||
|
||||
public bool IsAutoUpdateEnabled
|
||||
{
|
||||
get => _settingsService.IsAutoUpdateEnabled;
|
||||
|
||||
@@ -33,30 +33,40 @@
|
||||
|
||||
<UniformGrid
|
||||
Grid.Row="2"
|
||||
Margin="16"
|
||||
HorizontalAlignment="Right"
|
||||
Margin="8"
|
||||
Columns="{Binding ButtonsCount}">
|
||||
<!-- OK -->
|
||||
<Button
|
||||
Margin="8"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{Binding CloseCommand}"
|
||||
Content="{Binding DefaultButtonText}"
|
||||
IsDefault="True"
|
||||
IsVisible="{Binding IsDefaultButtonVisible}"
|
||||
Theme="{DynamicResource MaterialOutlineButton}">
|
||||
Theme="{DynamicResource MaterialOutlineButton}"
|
||||
ToolTip.Tip="{Binding DefaultButtonText}">
|
||||
<Button.CommandParameter>
|
||||
<system:Boolean>True</system:Boolean>
|
||||
</Button.CommandParameter>
|
||||
<TextBlock
|
||||
Text="{Binding DefaultButtonText}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Button>
|
||||
|
||||
<!-- Cancel -->
|
||||
<Button
|
||||
Margin="16,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="8"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
Command="{Binding CloseCommand}"
|
||||
Content="{Binding CancelButtonText}"
|
||||
IsCancel="True"
|
||||
IsVisible="{Binding IsCancelButtonVisible}"
|
||||
Theme="{DynamicResource MaterialOutlineButton}" />
|
||||
Theme="{DynamicResource MaterialOutlineButton}"
|
||||
ToolTip.Tip="{Binding CancelButtonText}">
|
||||
<TextBlock
|
||||
Text="{Binding CancelButtonText}"
|
||||
TextAlignment="Center"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
</Button>
|
||||
</UniformGrid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -50,8 +50,7 @@
|
||||
<!-- Auto-updates -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
IsVisible="{OnPlatform False,
|
||||
Windows=True}"
|
||||
IsVisible="{Binding IsAutoUpdateAvailable}"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="{Binding LocalizationManager.AutoUpdateTooltip}">
|
||||
<TextBlock DockPanel.Dock="Left" Text="{Binding LocalizationManager.AutoUpdateLabel}" />
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# DiscordChatExporter
|
||||
|
||||
[](https://github.com/Tyrrrz/.github/blob/master/docs/project-status.md)
|
||||
[](https://github.com/Tyrrrz/.github/blob/prime/docs/project-status.md)
|
||||
[](https://tyrrrz.me/ukraine)
|
||||
[](https://github.com/Tyrrrz/DiscordChatExporter/actions)
|
||||
[](https://codecov.io/gh/Tyrrrz/DiscordChatExporter)
|
||||
[](https://github.com/Tyrrrz/DiscordChatExporter/actions)
|
||||
[](https://codecov.io/gh/Tyrrrz/DiscordChatExporter)
|
||||
[](https://github.com/Tyrrrz/DiscordChatExporter/releases)
|
||||
[](https://github.com/Tyrrrz/DiscordChatExporter/releases)
|
||||
[](https://hub.docker.com/r/tyrrrz/discordchatexporter)
|
||||
@@ -27,7 +27,7 @@ It works with direct messages, group messages, and server channels, and supports
|
||||
|
||||
> 💬 If you want to chat, **join my [Discord server](https://discord.gg/2SUWKFnHSm)**.
|
||||
|
||||
## Terms of use<sup>[[?]](https://github.com/Tyrrrz/.github/blob/master/docs/why-so-political.md)</sup>
|
||||
## Terms of use<sup>[[?]](https://github.com/Tyrrrz/.github/blob/prime/docs/why-so-political.md)</sup>
|
||||
|
||||
By using this project or its source code, for any purpose and in any shape or form, you grant your **implicit agreement** to all the following statements:
|
||||
|
||||
@@ -43,23 +43,27 @@ To learn more about the war and how you can help, [click here](https://tyrrrz.me
|
||||
- **Graphical user interface** (desktop app):
|
||||
- 🟢 **[Stable release](https://github.com/Tyrrrz/DiscordChatExporter/releases/latest)**: look for `DiscordChatExporter.*.zip`
|
||||
- 🟠 [CI build](https://github.com/Tyrrrz/DiscordChatExporter/actions/workflows/main.yml): look for `DiscordChatExporter.*.zip`
|
||||
- 📦 [Scoop](https://scoop.sh/#/apps?q=DiscordChatExporter&p=1&id=c71b7367623c560a2dc746b9739b9568b79b59ae): `scoop install extras/discordchatexporter` (community-maintained)
|
||||
- 📦 [WinGet](https://winstall.app/apps/Tyrrrz.DiscordChatExporter.GUI): `winget install Tyrrrz.DiscordChatExporter.GUI` (community-maintained)
|
||||
- 📦 [AUR](https://aur.archlinux.org/packages/discord-chat-exporter-gui): `yay -S discord-chat-exporter-gui` (community-maintained)
|
||||
- 📦 [Nix](https://search.nixos.org/packages?show=discordchatexporter-desktop): `nix-shell -p discordchatexporter-desktop` (community-maintained)
|
||||
- **Command-line interface** (terminal app):
|
||||
- 🟢 **[Stable release](https://github.com/Tyrrrz/DiscordChatExporter/releases/latest)**: look for `DiscordChatExporter.Cli.*.zip`
|
||||
- 🟠 [CI build](https://github.com/Tyrrrz/DiscordChatExporter/actions/workflows/main.yml): look for `DiscordChatExporter.Cli.*.zip`
|
||||
- 🐋 [Docker](https://hub.docker.com/r/tyrrrz/discordchatexporter): `docker pull tyrrrz/discordchatexporter`
|
||||
- 📦 [AUR](https://aur.archlinux.org/packages/discord-chat-exporter-cli): `discord-chat-exporter-cli`
|
||||
- 📦 [Nix](https://search.nixos.org/packages?query=discordchatexporter-cli): `discordchatexporter-cli`
|
||||
- 📦 [WinGet](https://winstall.app/apps/Tyrrrz.DiscordChatExporter.CLI): `winget install Tyrrrz.DiscordChatExporter.CLI` (community-maintained)
|
||||
- 📦 [AUR](https://aur.archlinux.org/packages/discord-chat-exporter-cli): `yay -S discord-chat-exporter-cli` (community-maintained)
|
||||
- 📦 [Nix](https://search.nixos.org/packages?show=discordchatexporter-cli): `nix-shell -p discordchatexporter-cli` (community-maintained)
|
||||
|
||||
> [!IMPORTANT]
|
||||
> To launch the GUI version of the app on MacOS, you need to first remove the downloaded file from quarantine.
|
||||
> To launch the GUI version of the app on MacOS, you may need to first remove the downloaded file from quarantine.
|
||||
> You can do that by running the following command in the terminal: `xattr -rd com.apple.quarantine DiscordChatExporter.app`.
|
||||
|
||||
> [!NOTE]
|
||||
> If you're unsure which build is right for your system, consult with [this page](https://useragent.cc) to determine your OS and CPU architecture.
|
||||
> Community-maintained packages are published independently from this repository and may not always be up to date with the latest release.
|
||||
|
||||
> [!NOTE]
|
||||
> AUR and Nix packages linked above are maintained by the community.
|
||||
> If you have any issues with them, please contact the corresponding maintainers.
|
||||
> If you're unsure which build is right for your system, consult with [this page](https://useragent.cc) to determine your OS and CPU architecture.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 36 KiB |
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 26 KiB |
Reference in New Issue
Block a user