mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-07 04:39:33 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc35f25293 | ||
|
|
0e3969ca2d | ||
|
|
91b7486f45 | ||
|
|
b7548756a8 | ||
|
|
8f9c0f8660 | ||
|
|
b12be796c7 | ||
|
|
7a69c87b56 | ||
|
|
9e7ad4d85c | ||
|
|
0fb4bea64d | ||
|
|
d8abd011e5 | ||
|
|
9a5697b5b5 | ||
|
|
886b8442d3 | ||
|
|
bedf7de8c6 | ||
|
|
5ef329b63c | ||
|
|
060e55a0d7 | ||
|
|
61444aad30 | ||
|
|
9b3421e8a5 |
@@ -1,51 +0,0 @@
|
||||
# Install .NET runtime
|
||||
|
||||
**.NET 8.0 Runtime** is required by **DiscordChatExporter**.
|
||||
|
||||
> **Note**:
|
||||
> Installing the **.NET Runtime** is not required if you running **DiscordChatExporter** using [Docker](Docker.md).
|
||||
|
||||
## Windows
|
||||
|
||||
Both the GUI and the CLI flavors of **DiscordChatExporter** for Windows come with a bootstrapper that should automatically install all the required prerequisites. Simply run the application and follow the presented instructions.
|
||||
|
||||
If, for some reason, you need to install the runtime manually, use one of the download links below:
|
||||
|
||||
- [.NET 8.0 Runtime for **Windows x64**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-windows-x86-installer)
|
||||
- [.NET 8.0 Runtime for **Windows x86**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-windows-x64-installer)
|
||||
- [.NET 8.0 Runtime for **Windows arm64**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-windows-arm64-installer)
|
||||
|
||||
> **Note**:
|
||||
> [How can I tell if my computer is running a 32-bit (x86) or a 64-bit (x64) version of Windows?](https://support.microsoft.com/help/15056/windows-32-64-bit-faq)
|
||||
|
||||
## macOS
|
||||
|
||||
Use one of the download links below:
|
||||
|
||||
- [.NET 8.0 Runtime for **macOS x64**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-macos-x64-installer)
|
||||
- [.NET 8.0 Runtime for **macOS arm64**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-macos-arm64-installer)
|
||||
|
||||
> **Note**:
|
||||
> [Is my Mac using an Intel processor (x64) or Apple Silicon (arm64)?](https://support.apple.com/HT211814)
|
||||
|
||||
## Linux
|
||||
|
||||
Installing the .NET Runtime works differently depending on which Linux distribution you use. Check out the instructions for your distro:
|
||||
|
||||
- [Ubuntu](https://docs.microsoft.com/dotnet/core/install/linux-ubuntu)
|
||||
- [Debian](https://docs.microsoft.com/dotnet/core/install/linux-debian)
|
||||
- [Fedora](https://docs.microsoft.com/dotnet/core/install/linux-fedora)
|
||||
|
||||
For other distros, please check the _'Install on Linux'_ menu on the left of [this page](https://docs.microsoft.com/dotnet/core/install/linux).
|
||||
|
||||
## Verify installation (optional)
|
||||
|
||||
You can check which version of **.NET Runtime** is installed by running the following command in a terminal:
|
||||
|
||||
```console
|
||||
$ dotnet --info
|
||||
|
||||
.NET runtimes installed:
|
||||
Microsoft.NETCore.App 7.0.x [C:\path\to\dotnet\shared\Microsoft.NETCore.App]
|
||||
Microsoft.WindowsDesktop.App 7.0.x [C:\path\to\dotnet\shared\Microsoft.WindowsDesktop.App]
|
||||
```
|
||||
@@ -35,13 +35,13 @@ You can open `.html` files with a web browser, such as Google Chrome.
|
||||
|
||||
> **Warning**:
|
||||
> If a picture is deleted, or if a user changes its avatar, the respective images will no longer be displayed.
|
||||
> Export using the "Download referenced assets" (`--media`) option to avoid this.
|
||||
> Export using the "Download assets" (`--media`) option to avoid this.
|
||||
|
||||
### Plain Text
|
||||
|
||||
<img src="https://i.imgur.com/PbUyRXD.png" height="400"/>
|
||||
|
||||
The Plain Text format is the best option for archiving due to its small size.
|
||||
The Plain Text format formats messages as plain text, and has the smallest size.
|
||||
You can open `.txt` files with a text editor, such as Notepad.
|
||||
|
||||
### JSON
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
## Installation & Usage
|
||||
|
||||
- [Get .NET Runtime](Dotnet.md)
|
||||
- Getting started:
|
||||
- [Using the GUI](Using-the-GUI.md)
|
||||
- [Using the CLI](Using-the-CLI.md)
|
||||
@@ -28,6 +27,5 @@
|
||||
- [General questions](Troubleshooting.md#general)
|
||||
- [First steps help](Troubleshooting.md#first-steps)
|
||||
- [It's crashing/failing](Troubleshooting.md#DCE-is-crashingfailing)
|
||||
- [.NET Core Runtime is required](Troubleshooting.md#net-core-runtime-is-required)
|
||||
- [Errors](Troubleshooting.md#errors)
|
||||
- [**More help**](Troubleshooting.md)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Scheduling exports with Cron
|
||||
|
||||
Make sure you already have **DiscordChatExporter.CLI** downloaded and **.NET Runtime** installed.
|
||||
|
||||
## Creating the script
|
||||
|
||||
1. Open Terminal and create a new text file with `nano /path/to/DiscordChatExporter/cron.sh`
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# Scheduling exports on macOS
|
||||
|
||||
Make sure you already have **DiscordChatExporter.CLI** downloaded and **.NET Runtime** installed.
|
||||
|
||||
## Creating the script
|
||||
|
||||
1. Open TextEdit.app and create a new file
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
# Scheduling exports on Windows
|
||||
|
||||
We'll be using [DiscordChatExporter CLI](https://github.com/Tyrrrz/DiscordChatExporter/releases/latest), PowerShell, and Task Scheduler.
|
||||
Make sure you already have **DiscordChatExporter.CLI** downloaded and **.NET Runtime** installed.
|
||||
|
||||
## Creating the script
|
||||
|
||||
1. Open a text editor such as Notepad and paste:
|
||||
|
||||
@@ -70,7 +70,7 @@ Check the following page: [Obtaining token](Token-and-IDs.md)
|
||||
|
||||
### When I open DCE a black window pops up quickly or nothing shows up
|
||||
|
||||
If you have [.NET Runtime correctly installed](Dotnet.md), you might have downloaded the CLI flavor, try [downloading the GUI](Getting-started.md#gui-or-cli) instead.
|
||||
You might have downloaded the CLI flavor of the app, which is meant to be run in a terminal. Try [downloading the GUI](Getting-started.md#gui-or-cli) instead if that's what you want.
|
||||
|
||||
### How can I set DCE to export automatically at certain times?
|
||||
|
||||
@@ -84,16 +84,6 @@ Check the following pages to learn how to schedule **DiscordChatExporter.CLI** r
|
||||
|
||||
Try opening it with a different program, try partitioning or use a different file format, like `PlainText`.
|
||||
|
||||
### DCE is crashing/failing
|
||||
|
||||
Check the following page: [Installing .NET Core Runtime](Dotnet.md)
|
||||
|
||||
If you already have .NET Core installed, please check if your problem is a [known issue](https://github.com/Tyrrrz/DiscordChatExporter/issues?q=is%3Aissue) before [opening a bug report](https://github.com/Tyrrrz/DiscordChatExporter/issues/new).
|
||||
|
||||
### .NET Core Runtime is required
|
||||
|
||||
Check the following page: [Installing .NET Core Runtime](Dotnet.md)
|
||||
|
||||
### I see messages in the export, but they have no content
|
||||
|
||||
Your bot is missing the 'Message Content Intent'. Go to the [Discord Developer Portal](https://discord.com/developers/applications), navigate to the 'Bot' section and enable it.
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
# Using the CLI
|
||||
|
||||
## Guide
|
||||
|
||||
> **Note**:
|
||||
> Make sure you have [.NET Core installed](Dotnet.md) before attempting to run the commands below.
|
||||
> **Docker** users, please refer to the [Docker usage instructions](Docker.md).
|
||||
|
||||
## Step 1
|
||||
|
||||
After extracting the `.zip`, open Command Prompt, aka `cmd` (`Terminal` on **macOS** and **Linux**).
|
||||
|
||||
## Step 2
|
||||
|
||||
Change the current directory to DCE's folder with `cd C:\path\to\directory`, then press ENTER to run the command.
|
||||
Change the current directory to DCE's folder with `cd C:\path\to\DiscordChatExporter` (`cd /path/to/DiscordChatExporter` on **MacOS** and **Linux**), then press ENTER to run the command.
|
||||
|
||||
**Windows** users can quickly get the directory's path by clicking the address bar while inside the folder.
|
||||
**Windows** users can quickly get the folder's path by clicking the address bar while inside the folder.
|
||||

|
||||
|
||||
**macOS** users can select the `.exe`, hit Command+I (⌘I), and copy what's after `Where:` to get the directory
|
||||

|
||||
**macOS** users can press Command+Option+C (⌘⌥C) while inside the folder (or selecting it) to copy its path to the clipboard.
|
||||
|
||||
You can also drag and drop the folder on **every platform**.
|
||||

|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
### Step 1
|
||||
|
||||
After extracting the `.zip`, open `DiscordChatExporter.exe`
|
||||
After extracting the `.zip`, run `DiscordChatExporter.exe` (Windows), or `DiscordChatExporter` (Mac OS and Linux).
|
||||
|
||||
### Step 2
|
||||
|
||||
@@ -61,37 +61,30 @@ In this screen you can customize the following:
|
||||
|
||||
## Settings
|
||||
|
||||
- **Auto-update**
|
||||
Perform automatic updates on every launch.
|
||||
- **Auto-update** - Perform automatic updates on every launch.
|
||||
Default: Enabled
|
||||
|
||||
> **Note**:
|
||||
> Keep this option enabled to receive the latest features and bug fixes!
|
||||
|
||||
- **Dark mode**
|
||||
Use darker colors in the UI (User Interface).
|
||||
- **Dark mode** - Use darker colors in the UI (User Interface).
|
||||
Default: Disabled
|
||||
|
||||
- **Persist token**
|
||||
Persist last used token between sessions.
|
||||
- **Persist token** - Persist last used token between sessions.
|
||||
Default: Enabled
|
||||
|
||||
- **Show threads**
|
||||
Controls whether threads are shown in the channel list.
|
||||
- **Show threads** - Controls whether threads are shown in the channel list.
|
||||
Default: none
|
||||
|
||||
- **Locale**
|
||||
Customize how dates are formatted in the exported files.
|
||||
- **Locale** - Customize how dates are formatted in the exported files.
|
||||
|
||||
- **Date format**
|
||||
Customize how dates are formatted in the exported files in the settings menu ().
|
||||
- **Date format** - Customize how dates are formatted in the exported files in the settings menu ().
|
||||
|
||||
- **Parallel limit**
|
||||
The number of channels that will be exported at the same time.
|
||||
- **Parallel limit** - The number of channels that will be exported at the same time.
|
||||
Default: 1
|
||||
|
||||
- **Normalize to UTC**
|
||||
Convert all dates to UTC before exporting.
|
||||
|
||||
> **Note**:
|
||||
> Try to keep this number low so that your account doesn't get flagged.
|
||||
|
||||
- **Normalize to UTC** - Convert all dates to UTC before exporting.
|
||||
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
2
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@@ -100,6 +100,8 @@ body:
|
||||
required: true
|
||||
- label: I have sponsored this project
|
||||
required: false
|
||||
- label: I have not read any of the above and just checked all the boxes to submit the issue
|
||||
required: false
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
|
||||
11
.github/workflows/main.yml
vendored
11
.github/workflows/main.yml
vendored
@@ -99,8 +99,7 @@ jobs:
|
||||
# GUI assets aren't suffixed, unlike the CLI assets
|
||||
asset: DiscordChatExporter
|
||||
|
||||
# Need to run on Windows because of DotnetRuntimeBootstrapper's dependency on Ressy
|
||||
runs-on: windows-latest
|
||||
runs-on: ${{ startsWith(matrix.rid, 'win-') && 'windows-latest' || startsWith(matrix.rid, 'osx-') && 'macos-latest' || 'ubuntu-latest' }}
|
||||
timeout-minutes: 10
|
||||
|
||||
permissions:
|
||||
@@ -124,7 +123,7 @@ jobs:
|
||||
--output ${{ matrix.app }}/bin/publish/
|
||||
--configuration Release
|
||||
--runtime ${{ matrix.rid }}
|
||||
--no-self-contained
|
||||
--self-contained
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
@@ -196,7 +195,11 @@ jobs:
|
||||
with:
|
||||
name: ${{ matrix.asset }}.${{ matrix.rid }}
|
||||
path: ${{ matrix.app }}/
|
||||
|
||||
|
||||
- name: Set permissions
|
||||
if: ${{ !startsWith(matrix.rid, 'win') }}
|
||||
run: chmod +x ${{ matrix.app }}/${{ matrix.asset }}
|
||||
|
||||
- name: Create package
|
||||
# Change into the artifacts directory to avoid including the directory itself in the zip archive
|
||||
working-directory: ${{ matrix.app }}/
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<ILLinkTreatWarningsAsErrors>false</ILLinkTreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -20,7 +20,7 @@
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" Version="2.8.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<NoWarn>$(NoWarn);IL2104</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliFx" Version="2.3.5" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.49.1" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,3 +1,37 @@
|
||||
using CliFx;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
using CliFx;
|
||||
using DiscordChatExporter.Cli.Commands;
|
||||
using DiscordChatExporter.Cli.Commands.Converters;
|
||||
|
||||
return await new CliApplicationBuilder().AddCommandsFromThisAssembly().Build().RunAsync(args);
|
||||
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))]
|
||||
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>()
|
||||
.Build()
|
||||
.RunAsync(args);
|
||||
}
|
||||
|
||||
@@ -676,10 +676,14 @@ public class DiscordClient(string token)
|
||||
.SetQueryParameter("after", currentAfter.ToString())
|
||||
.Build();
|
||||
|
||||
var response = await GetJsonResponseAsync(url, cancellationToken);
|
||||
// Can be null on reactions with an emoji that has been deleted (?)
|
||||
// https://github.com/Tyrrrz/DiscordChatExporter/issues/1226
|
||||
var response = await TryGetJsonResponseAsync(url, cancellationToken);
|
||||
if (response is null)
|
||||
yield break;
|
||||
|
||||
var count = 0;
|
||||
foreach (var userJson in response.EnumerateArray())
|
||||
foreach (var userJson in response.Value.EnumerateArray())
|
||||
{
|
||||
var user = User.Parse(userJson);
|
||||
yield return user;
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="JsonExtensions" Version="1.2.0" />
|
||||
<PackageReference Include="Polly" Version="8.3.1" />
|
||||
<PackageReference Include="Polly" Version="8.4.0" />
|
||||
<PackageReference Include="RazorBlade" Version="0.6.0" />
|
||||
<PackageReference Include="Superpower" Version="3.0.0" />
|
||||
<PackageReference Include="WebMarkupMin.Core" Version="2.16.0" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.3.14" />
|
||||
<PackageReference Include="YoutubeExplode" Version="6.3.16" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -7,13 +7,18 @@
|
||||
xmlns:materialAssists="clr-namespace:Material.Styles.Assists;assembly=Material.Styles"
|
||||
xmlns:materialControls="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles">
|
||||
xmlns:materialStyles="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"
|
||||
ActualThemeVariantChanged="Application_OnActualThemeVariantChanged">
|
||||
<Application.DataTemplates>
|
||||
<framework:ViewManager />
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<materialStyles:MaterialTheme />
|
||||
<!-- This theme is used as a stub to pre-load default resources, the actual colors are set through code -->
|
||||
<materialStyles:MaterialTheme
|
||||
BaseTheme="Light"
|
||||
PrimaryColor="Grey"
|
||||
SecondaryColor="DeepOrange" />
|
||||
<materialIcons:MaterialIconStyles />
|
||||
<dialogHostAvalonia:DialogHostStyles />
|
||||
|
||||
@@ -121,7 +126,7 @@
|
||||
|
||||
<!-- Toggle switch -->
|
||||
<Style Selector="ToggleSwitch">
|
||||
<Setter Property="materialAssists:ToggleSwitchAssist.SwitchThumbOffBackground" Value="{DynamicResource MaterialBackgroundBrush}" />
|
||||
<Setter Property="materialAssists:ToggleSwitchAssist.SwitchThumbOffBackground" Value="{DynamicResource ToggleBackgroundBrush}" />
|
||||
</Style>
|
||||
|
||||
<!-- Tooltip -->
|
||||
@@ -132,4 +137,17 @@
|
||||
<Setter Property="TextElement.FontStretch" Value="Normal" />
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Default">
|
||||
<SolidColorBrush x:Key="ToggleBackgroundBrush" Color="#FFFFFF" />
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<SolidColorBrush x:Key="ToggleBackgroundBrush" Color="#8E8E8E" />
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -7,6 +7,8 @@ using Avalonia.Media;
|
||||
using Avalonia.Platform;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Services;
|
||||
using DiscordChatExporter.Gui.Utils;
|
||||
using DiscordChatExporter.Gui.Utils.Extensions;
|
||||
using DiscordChatExporter.Gui.ViewModels;
|
||||
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
@@ -16,9 +18,12 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DiscordChatExporter.Gui;
|
||||
|
||||
public partial class App : Application, IDisposable
|
||||
public class App : Application, IDisposable
|
||||
{
|
||||
private readonly DisposableCollector _eventRoot = new();
|
||||
|
||||
private readonly ServiceProvider _services;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly MainViewModel _mainViewModel;
|
||||
|
||||
public App()
|
||||
@@ -43,17 +48,54 @@ public partial class App : Application, IDisposable
|
||||
services.AddTransient<SettingsViewModel>();
|
||||
|
||||
_services = services.BuildServiceProvider(true);
|
||||
_settingsService = _services.GetRequiredService<SettingsService>();
|
||||
_mainViewModel = _services.GetRequiredService<ViewModelManager>().CreateMainViewModel();
|
||||
|
||||
// Re-initialize the theme when the user changes it
|
||||
_eventRoot.Add(
|
||||
_settingsService.WatchProperty(
|
||||
o => o.Theme,
|
||||
() =>
|
||||
{
|
||||
RequestedThemeVariant = _settingsService.Theme switch
|
||||
{
|
||||
ThemeVariant.Light => Avalonia.Styling.ThemeVariant.Light,
|
||||
ThemeVariant.Dark => Avalonia.Styling.ThemeVariant.Dark,
|
||||
_ => Avalonia.Styling.ThemeVariant.Default
|
||||
};
|
||||
|
||||
InitializeTheme();
|
||||
},
|
||||
false
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// Increase maximum concurrent connections
|
||||
ServicePointManager.DefaultConnectionLimit = 20;
|
||||
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
private void InitializeTheme()
|
||||
{
|
||||
var actualTheme = RequestedThemeVariant?.Key switch
|
||||
{
|
||||
"Light" => PlatformThemeVariant.Light,
|
||||
"Dark" => PlatformThemeVariant.Dark,
|
||||
_ => PlatformSettings?.GetColorValues().ThemeVariant ?? PlatformThemeVariant.Light
|
||||
};
|
||||
|
||||
this.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme =
|
||||
actualTheme == PlatformThemeVariant.Light
|
||||
? Theme.Create(Theme.Light, Color.Parse("#343838"), Color.Parse("#F9A825"))
|
||||
: Theme.Create(Theme.Dark, Color.Parse("#E8E8E8"), Color.Parse("#F9A825"));
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
@@ -61,50 +103,20 @@ public partial class App : Application, IDisposable
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
|
||||
// Set custom theme colors
|
||||
SetDefaultTheme();
|
||||
// Set up custom theme colors
|
||||
InitializeTheme();
|
||||
|
||||
// Load settings
|
||||
_settingsService.Load();
|
||||
}
|
||||
|
||||
public void Dispose() => _services.Dispose();
|
||||
}
|
||||
private void Application_OnActualThemeVariantChanged(object? sender, EventArgs args) =>
|
||||
// Re-initialize the theme when the system theme changes
|
||||
InitializeTheme();
|
||||
|
||||
public partial class App
|
||||
{
|
||||
public static void SetLightTheme()
|
||||
public void Dispose()
|
||||
{
|
||||
if (Current is null)
|
||||
return;
|
||||
|
||||
Current.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme = Theme.Create(
|
||||
Theme.Light,
|
||||
Color.Parse("#343838"),
|
||||
Color.Parse("#F9A825")
|
||||
);
|
||||
}
|
||||
|
||||
public static void SetDarkTheme()
|
||||
{
|
||||
if (Current is null)
|
||||
return;
|
||||
|
||||
Current.LocateMaterialTheme<MaterialThemeBase>().CurrentTheme = Theme.Create(
|
||||
Theme.Dark,
|
||||
Color.Parse("#E8E8E8"),
|
||||
Color.Parse("#F9A825")
|
||||
);
|
||||
}
|
||||
|
||||
public static void SetDefaultTheme()
|
||||
{
|
||||
if (Current is null)
|
||||
return;
|
||||
|
||||
var isDarkModeEnabledByDefault =
|
||||
Current.PlatformSettings?.GetColorValues().ThemeVariant == PlatformThemeVariant.Dark;
|
||||
|
||||
if (isDarkModeEnabledByDefault)
|
||||
SetDarkTheme();
|
||||
else
|
||||
SetLightTheme();
|
||||
_eventRoot.Dispose();
|
||||
_services.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class LocaleToDisplayNameStringConverter : IValueConverter
|
||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
|
||||
value is string locale && !string.IsNullOrWhiteSpace(locale)
|
||||
? CultureInfo.GetCultureInfo(locale).DisplayName
|
||||
: "System default";
|
||||
: "System";
|
||||
|
||||
public object ConvertBack(
|
||||
object? value,
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AssemblyName>DiscordChatExporter</AssemblyName>
|
||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -15,14 +17,13 @@
|
||||
<PackageReference Include="Avalonia" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.10" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.10" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="Cogwheel" Version="2.0.4" />
|
||||
<PackageReference Include="Cogwheel" Version="2.1.0" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
<PackageReference Include="CSharpier.MsBuild" Version="0.28.2" PrivateAssets="all" />
|
||||
<PackageReference Include="Deorcify" Version="1.0.2" PrivateAssets="all" />
|
||||
<PackageReference Include="DialogHost.Avalonia" Version="0.7.7" />
|
||||
<PackageReference Include="DotnetRuntimeBootstrapper" Version="2.5.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Gress" Version="2.1.1" />
|
||||
<PackageReference Include="Material.Avalonia" Version="3.5.0" />
|
||||
<PackageReference Include="Material.Avalonia" Version="3.6.0" />
|
||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Onova" Version="2.6.11" />
|
||||
|
||||
8
DiscordChatExporter.Gui/Framework/ThemeVariant.cs
Normal file
8
DiscordChatExporter.Gui/Framework/ThemeVariant.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DiscordChatExporter.Gui.Framework;
|
||||
|
||||
public enum ThemeVariant
|
||||
{
|
||||
System,
|
||||
Light,
|
||||
Dark
|
||||
}
|
||||
@@ -1,25 +1,31 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using DiscordChatExporter.Gui.ViewModels;
|
||||
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
using DiscordChatExporter.Gui.Views;
|
||||
using DiscordChatExporter.Gui.Views.Components;
|
||||
using DiscordChatExporter.Gui.Views.Dialogs;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Framework;
|
||||
|
||||
public partial class ViewManager
|
||||
{
|
||||
private Control? TryCreateView(ViewModelBase viewModel) =>
|
||||
viewModel switch
|
||||
{
|
||||
MainViewModel => new MainView(),
|
||||
DashboardViewModel => new DashboardView(),
|
||||
ExportSetupViewModel => new ExportSetupView(),
|
||||
MessageBoxViewModel => new MessageBoxView(),
|
||||
SettingsViewModel => new SettingsView(),
|
||||
_ => null
|
||||
};
|
||||
|
||||
public Control? TryBindView(ViewModelBase viewModel)
|
||||
{
|
||||
var name = viewModel
|
||||
.GetType()
|
||||
.FullName?.Replace("ViewModel", "View", StringComparison.Ordinal);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return null;
|
||||
|
||||
var type = Type.GetType(name);
|
||||
if (type is null)
|
||||
return null;
|
||||
|
||||
if (Activator.CreateInstance(type) is not Control view)
|
||||
var view = TryCreateView(viewModel);
|
||||
if (view is null)
|
||||
return null;
|
||||
|
||||
view.DataContext ??= viewModel;
|
||||
|
||||
@@ -17,8 +17,6 @@ public static class Program
|
||||
|
||||
public static string ProjectUrl { get; } = "https://github.com/Tyrrrz/DiscordChatExporter";
|
||||
|
||||
public static string LatestReleaseUrl { get; } = ProjectUrl + "/releases/latest";
|
||||
|
||||
public static string DocumentationUrl { get; } = ProjectUrl + "/tree/master/.docs";
|
||||
|
||||
public static AppBuilder BuildAvaloniaApp() =>
|
||||
|
||||
@@ -1,79 +1,133 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Platform;
|
||||
using System.Text.Json.Serialization;
|
||||
using Cogwheel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using DiscordChatExporter.Core.Exporting;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Models;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Services;
|
||||
|
||||
// Can't use [ObservableProperty] here because System.Text.Json's source generator doesn't see
|
||||
// the generated properties.
|
||||
[INotifyPropertyChanged]
|
||||
public partial class SettingsService()
|
||||
: SettingsBase(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.dat"))
|
||||
: SettingsBase(
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Settings.dat"),
|
||||
SerializerContext.Default
|
||||
)
|
||||
{
|
||||
[ObservableProperty]
|
||||
private bool _isUkraineSupportMessageEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isAutoUpdateEnabled = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDarkModeEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isTokenPersisted = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private ThreadInclusionMode _threadInclusionMode;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _locale;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isUtcNormalizationEnabled;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _parallelLimit = 1;
|
||||
|
||||
[ObservableProperty]
|
||||
private Version? _lastAppVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _lastToken;
|
||||
|
||||
[ObservableProperty]
|
||||
private ExportFormat _lastExportFormat = ExportFormat.HtmlDark;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _lastPartitionLimitValue;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _lastMessageFilterValue;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _lastShouldFormatMarkdown = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _lastShouldDownloadAssets;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _lastShouldReuseAssets;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _lastAssetsDirPath;
|
||||
|
||||
public override void Reset()
|
||||
public bool IsUkraineSupportMessageEnabled
|
||||
{
|
||||
base.Reset();
|
||||
get => _isUkraineSupportMessageEnabled;
|
||||
set => SetProperty(ref _isUkraineSupportMessageEnabled, value);
|
||||
}
|
||||
|
||||
// Reset the dark mode setting separately because its default value is evaluated dynamically
|
||||
// and cannot be set by the field initializer.
|
||||
IsDarkModeEnabled =
|
||||
Application.Current?.PlatformSettings?.GetColorValues().ThemeVariant
|
||||
== PlatformThemeVariant.Dark;
|
||||
private ThemeVariant _theme;
|
||||
public ThemeVariant Theme
|
||||
{
|
||||
get => _theme;
|
||||
set => SetProperty(ref _theme, value);
|
||||
}
|
||||
|
||||
private bool _isAutoUpdateEnabled = true;
|
||||
public bool IsAutoUpdateEnabled
|
||||
{
|
||||
get => _isAutoUpdateEnabled;
|
||||
set => SetProperty(ref _isAutoUpdateEnabled, value);
|
||||
}
|
||||
|
||||
private bool _isTokenPersisted = true;
|
||||
public bool IsTokenPersisted
|
||||
{
|
||||
get => _isTokenPersisted;
|
||||
set => SetProperty(ref _isTokenPersisted, value);
|
||||
}
|
||||
|
||||
private ThreadInclusionMode _threadInclusionMode;
|
||||
public ThreadInclusionMode ThreadInclusionMode
|
||||
{
|
||||
get => _threadInclusionMode;
|
||||
set => SetProperty(ref _threadInclusionMode, value);
|
||||
}
|
||||
|
||||
private string? _locale;
|
||||
public string? Locale
|
||||
{
|
||||
get => _locale;
|
||||
set => SetProperty(ref _locale, value);
|
||||
}
|
||||
|
||||
private bool _isUtcNormalizationEnabled;
|
||||
public bool IsUtcNormalizationEnabled
|
||||
{
|
||||
get => _isUtcNormalizationEnabled;
|
||||
set => SetProperty(ref _isUtcNormalizationEnabled, value);
|
||||
}
|
||||
|
||||
private int _parallelLimit = 1;
|
||||
public int ParallelLimit
|
||||
{
|
||||
get => _parallelLimit;
|
||||
set => SetProperty(ref _parallelLimit, value);
|
||||
}
|
||||
|
||||
private string? _lastToken;
|
||||
public string? LastToken
|
||||
{
|
||||
get => _lastToken;
|
||||
set => SetProperty(ref _lastToken, value);
|
||||
}
|
||||
|
||||
private ExportFormat _lastExportFormat = ExportFormat.HtmlDark;
|
||||
public ExportFormat LastExportFormat
|
||||
{
|
||||
get => _lastExportFormat;
|
||||
set => SetProperty(ref _lastExportFormat, value);
|
||||
}
|
||||
|
||||
private string? _lastPartitionLimitValue;
|
||||
public string? LastPartitionLimitValue
|
||||
{
|
||||
get => _lastPartitionLimitValue;
|
||||
set => SetProperty(ref _lastPartitionLimitValue, value);
|
||||
}
|
||||
|
||||
private string? _lastMessageFilterValue;
|
||||
public string? LastMessageFilterValue
|
||||
{
|
||||
get => _lastMessageFilterValue;
|
||||
set => SetProperty(ref _lastMessageFilterValue, value);
|
||||
}
|
||||
|
||||
private bool _lastShouldFormatMarkdown = true;
|
||||
public bool LastShouldFormatMarkdown
|
||||
{
|
||||
get => _lastShouldFormatMarkdown;
|
||||
set => SetProperty(ref _lastShouldFormatMarkdown, value);
|
||||
}
|
||||
|
||||
private bool _lastShouldDownloadAssets;
|
||||
public bool LastShouldDownloadAssets
|
||||
{
|
||||
get => _lastShouldDownloadAssets;
|
||||
set => SetProperty(ref _lastShouldDownloadAssets, value);
|
||||
}
|
||||
|
||||
private bool _lastShouldReuseAssets;
|
||||
public bool LastShouldReuseAssets
|
||||
{
|
||||
get => _lastShouldReuseAssets;
|
||||
set => SetProperty(ref _lastShouldReuseAssets, value);
|
||||
}
|
||||
|
||||
private string? _lastAssetsDirPath;
|
||||
public string? LastAssetsDirPath
|
||||
{
|
||||
get => _lastAssetsDirPath;
|
||||
set => SetProperty(ref _lastAssetsDirPath, value);
|
||||
}
|
||||
|
||||
public override void Save()
|
||||
@@ -88,3 +142,9 @@ public partial class SettingsService()
|
||||
LastToken = lastToken;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class SettingsService
|
||||
{
|
||||
[JsonSerializable(typeof(SettingsService))]
|
||||
private partial class SerializerContext : JsonSerializerContext;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using DiscordChatExporter.Core.Utils.Extensions;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Models;
|
||||
@@ -23,25 +22,27 @@ public class SettingsViewModel : DialogViewModelBase
|
||||
_eventRoot.Add(_settingsService.WatchAllProperties(OnAllPropertiesChanged));
|
||||
}
|
||||
|
||||
public IReadOnlyList<ThemeVariant> AvailableThemes { get; } = Enum.GetValues<ThemeVariant>();
|
||||
|
||||
public ThemeVariant Theme
|
||||
{
|
||||
get => _settingsService.Theme;
|
||||
set => _settingsService.Theme = value;
|
||||
}
|
||||
|
||||
public bool IsAutoUpdateEnabled
|
||||
{
|
||||
get => _settingsService.IsAutoUpdateEnabled;
|
||||
set => _settingsService.IsAutoUpdateEnabled = value;
|
||||
}
|
||||
|
||||
public bool IsDarkModeEnabled
|
||||
{
|
||||
get => _settingsService.IsDarkModeEnabled;
|
||||
set => _settingsService.IsDarkModeEnabled = value;
|
||||
}
|
||||
|
||||
public bool IsTokenPersisted
|
||||
{
|
||||
get => _settingsService.IsTokenPersisted;
|
||||
set => _settingsService.IsTokenPersisted = value;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ThreadInclusionMode> AvailableThreadInclusions { get; } =
|
||||
public IReadOnlyList<ThreadInclusionMode> AvailableThreadInclusionModes { get; } =
|
||||
Enum.GetValues<ThreadInclusionMode>();
|
||||
|
||||
public ThreadInclusionMode ThreadInclusionMode
|
||||
|
||||
@@ -79,36 +79,8 @@ public partial class MainViewModel(
|
||||
[RelayCommand]
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
// Reset settings (needed to resolve the default dark mode setting)
|
||||
settingsService.Reset();
|
||||
|
||||
// Load settings
|
||||
settingsService.Load();
|
||||
|
||||
// Set the correct theme
|
||||
if (settingsService.IsDarkModeEnabled)
|
||||
App.SetDarkTheme();
|
||||
else
|
||||
App.SetLightTheme();
|
||||
|
||||
await ShowUkraineSupportMessageAsync();
|
||||
await CheckForUpdatesAsync();
|
||||
|
||||
// App has just been updated, display the changelog
|
||||
if (
|
||||
settingsService.LastAppVersion is not null
|
||||
&& settingsService.LastAppVersion != Program.Version
|
||||
)
|
||||
{
|
||||
snackbarManager.Notify(
|
||||
$"Successfully updated to {Program.Name} v{Program.VersionString}",
|
||||
"WHAT'S NEW",
|
||||
() => ProcessEx.StartShellExecute(Program.LatestReleaseUrl)
|
||||
);
|
||||
|
||||
settingsService.LastAppVersion = Program.Version;
|
||||
settingsService.Save();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
|
||||
@@ -9,11 +9,8 @@
|
||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||
xmlns:materialStyles="clr-namespace:Material.Styles.Controls;assembly=Material.Styles"
|
||||
x:Name="UserControl"
|
||||
x:DataType="components:DashboardViewModel"
|
||||
Loaded="UserControl_OnLoaded">
|
||||
<Design.DataContext>
|
||||
<components:DashboardViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<DockPanel>
|
||||
<!-- Header -->
|
||||
<StackPanel
|
||||
@@ -95,6 +92,130 @@
|
||||
</Style>
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
<!-- Guilds and channels -->
|
||||
<Grid ColumnDefinitions="Auto,*" IsVisible="{Binding !!AvailableGuilds.Count}">
|
||||
<!-- Guilds -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
BorderBrush="{DynamicResource MaterialDividerBrush}"
|
||||
BorderThickness="0,0,1,0">
|
||||
<ListBox
|
||||
x:Name="AvailableGuildsListBox"
|
||||
ItemsSource="{Binding AvailableGuilds}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
SelectedItem="{Binding SelectedGuild}"
|
||||
SelectionChanged="AvailableGuildsListBox_OnSelectionChanged"
|
||||
SelectionMode="Single">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBox">
|
||||
<Style Selector="^ ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Background="Transparent" ToolTip.Tip="{Binding Name}">
|
||||
<!-- Guild icon placeholder -->
|
||||
<Ellipse
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="12"
|
||||
Fill="{DynamicResource MaterialDividerBrush}" />
|
||||
|
||||
<!-- Guild icon -->
|
||||
<Ellipse
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="12">
|
||||
<Ellipse.Fill>
|
||||
<ImageBrush asyncImageLoader:ImageBrushLoader.Source="{Binding IconUrl}" />
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
<!-- Channels -->
|
||||
<Border Grid.Column="1">
|
||||
<TreeView
|
||||
x:Name="AvailableChannelsTreeView"
|
||||
ItemsSource="{Binding AvailableChannels}"
|
||||
SelectedItems="{Binding SelectedChannels}"
|
||||
SelectionChanged="AvailableChannelsTreeView_OnSelectionChanged"
|
||||
SelectionMode="Multiple"
|
||||
TextSearch.Text="Name">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeView">
|
||||
<Style Selector="^ TreeViewItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||
<Grid
|
||||
Background="Transparent"
|
||||
Classes.category="{Binding Channel.IsCategory}"
|
||||
ColumnDefinitions="Auto,*,Auto">
|
||||
<Grid.Styles>
|
||||
<Style Selector="Grid">
|
||||
<Style Selector="^:not(.category)">
|
||||
<Setter Property="ToolTip.Tip">
|
||||
<Template>
|
||||
<TextBlock>
|
||||
<Run Text="Last message sent:" />
|
||||
<Run FontWeight="SemiBold" Text="{Binding Channel.LastMessageId, Converter={x:Static converters:SnowflakeToTimestampStringConverter.Instance}, TargetNullValue=never, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</Template>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<!-- Channel icon -->
|
||||
<materialIcons:MaterialIcon
|
||||
Grid.Column="0"
|
||||
Margin="0,0,4,0"
|
||||
Classes.voice="{Binding Channel.IsVoice}"
|
||||
IsVisible="{Binding !Channel.IsCategory}">
|
||||
<materialIcons:MaterialIcon.Styles>
|
||||
<Style Selector="materialIcons|MaterialIcon">
|
||||
<Setter Property="Kind" Value="Pound" />
|
||||
|
||||
<Style Selector="^.voice">
|
||||
<Setter Property="Kind" Value="VolumeHigh" />
|
||||
</Style>
|
||||
</Style>
|
||||
</materialIcons:MaterialIcon.Styles>
|
||||
</materialIcons:MaterialIcon>
|
||||
|
||||
<!-- Channel name -->
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="0,12"
|
||||
FontSize="14"
|
||||
Text="{Binding Channel.Name, Mode=OneWay}" />
|
||||
|
||||
<!-- Checkmark -->
|
||||
<materialIcons:MaterialIcon
|
||||
Grid.Column="2"
|
||||
Width="24"
|
||||
Height="24"
|
||||
Margin="16,0"
|
||||
IsVisible="{Binding $parent[TreeViewItem].IsSelected}"
|
||||
Kind="Check" />
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Placeholder / usage instructions -->
|
||||
<Panel IsVisible="{Binding !AvailableGuilds.Count}">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
@@ -215,131 +336,6 @@
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
|
||||
<!-- Guilds and channels -->
|
||||
<Grid ColumnDefinitions="Auto,*" IsVisible="{Binding !!AvailableGuilds.Count}">
|
||||
<!-- Guilds -->
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
BorderBrush="{DynamicResource MaterialDividerBrush}"
|
||||
BorderThickness="0,0,1,0">
|
||||
<ListBox
|
||||
x:Name="AvailableGuildsListBox"
|
||||
ItemsSource="{Binding AvailableGuilds}"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
SelectedItem="{Binding SelectedGuild}"
|
||||
SelectionChanged="AvailableGuildsListBox_OnSelectionChanged"
|
||||
SelectionMode="Single">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBox">
|
||||
<Style Selector="^ ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Panel Background="Transparent" ToolTip.Tip="{Binding Name}">
|
||||
<!-- Guild icon placeholder -->
|
||||
<Ellipse
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="12"
|
||||
Fill="{DynamicResource MaterialDividerBrush}" />
|
||||
|
||||
<!-- Guild icon -->
|
||||
<Ellipse
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="12">
|
||||
<Ellipse.Fill>
|
||||
<ImageBrush asyncImageLoader:ImageBrushLoader.Source="{Binding IconUrl}" />
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Border>
|
||||
|
||||
<!-- Channels -->
|
||||
<Border Grid.Column="1">
|
||||
<TreeView
|
||||
x:Name="AvailableChannelsTreeView"
|
||||
AutoScrollToSelectedItem="False"
|
||||
ItemsSource="{Binding AvailableChannels}"
|
||||
SelectedItems="{Binding SelectedChannels}"
|
||||
SelectionChanged="AvailableChannelsTreeView_OnSelectionChanged"
|
||||
SelectionMode="Multiple"
|
||||
TextSearch.Text="Name">
|
||||
<TreeView.Styles>
|
||||
<Style Selector="TreeView">
|
||||
<Style Selector="^ TreeViewItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
</Style>
|
||||
</Style>
|
||||
</TreeView.Styles>
|
||||
<TreeView.ItemTemplate>
|
||||
<TreeDataTemplate ItemsSource="{Binding Children}">
|
||||
<Grid
|
||||
Background="Transparent"
|
||||
Classes.category="{Binding Channel.IsCategory}"
|
||||
ColumnDefinitions="Auto,*,Auto">
|
||||
<Grid.Styles>
|
||||
<Style Selector="Grid">
|
||||
<Style Selector="^:not(.category)">
|
||||
<Setter Property="ToolTip.Tip">
|
||||
<Template>
|
||||
<TextBlock>
|
||||
<Run Text="Last message sent:" />
|
||||
<Run FontWeight="SemiBold" Text="{Binding Channel.LastMessageId, Converter={x:Static converters:SnowflakeToTimestampStringConverter.Instance}, TargetNullValue=never, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</Template>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
|
||||
<!-- Channel icon -->
|
||||
<materialIcons:MaterialIcon
|
||||
Grid.Column="0"
|
||||
Margin="0,0,4,0"
|
||||
Classes.voice="{Binding Channel.IsVoice}"
|
||||
IsVisible="{Binding !Channel.IsCategory}">
|
||||
<materialIcons:MaterialIcon.Styles>
|
||||
<Style Selector="materialIcons|MaterialIcon">
|
||||
<Setter Property="Kind" Value="Pound" />
|
||||
|
||||
<Style Selector="^.voice">
|
||||
<Setter Property="Kind" Value="VolumeHigh" />
|
||||
</Style>
|
||||
</Style>
|
||||
</materialIcons:MaterialIcon.Styles>
|
||||
</materialIcons:MaterialIcon>
|
||||
|
||||
<!-- Channel name -->
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Margin="0,12"
|
||||
FontSize="14"
|
||||
Text="{Binding Channel.Name, Mode=OneWay}" />
|
||||
|
||||
<!-- Checkmark -->
|
||||
<materialIcons:MaterialIcon
|
||||
Grid.Column="2"
|
||||
Width="24"
|
||||
Height="24"
|
||||
Margin="16,0"
|
||||
IsVisible="{Binding $parent[TreeViewItem].IsSelected}"
|
||||
Kind="Check" />
|
||||
</Grid>
|
||||
</TreeDataTemplate>
|
||||
</TreeView.ItemTemplate>
|
||||
</TreeView>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Export button -->
|
||||
<Button
|
||||
Width="56"
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<UserControl
|
||||
x:Class="DiscordChatExporter.Gui.Views.Controls.HyperLink"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:DiscordChatExporter.Gui.Views.Controls">
|
||||
<TextBlock
|
||||
x:Name="TextBlock"
|
||||
Cursor="Hand"
|
||||
Foreground="{DynamicResource MaterialSecondaryDarkBrush}"
|
||||
PointerReleased="TextBlock_OnPointerReleased"
|
||||
Text="{Binding $parent[UserControl].Text, Mode=OneWay}">
|
||||
Text="{Binding $parent[controls:HyperLink].Text, Mode=OneWay}">
|
||||
<TextBlock.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Style Selector="^:pointerover">
|
||||
|
||||
@@ -10,11 +10,8 @@
|
||||
xmlns:utils="clr-namespace:DiscordChatExporter.Gui.Utils"
|
||||
x:Name="UserControl"
|
||||
Width="380"
|
||||
x:DataType="dialogs:ExportSetupViewModel"
|
||||
Loaded="UserControl_OnLoaded">
|
||||
<Design.DataContext>
|
||||
<dialogs:ExportSetupViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<!-- Guild/channel info -->
|
||||
<Grid
|
||||
@@ -40,7 +37,7 @@
|
||||
FontWeight="Light"
|
||||
IsVisible="{Binding !IsSingleChannel}"
|
||||
TextTrimming="CharacterEllipsis">
|
||||
<Run Text="{Binding Channels.Count, Mode=OneWay}" />
|
||||
<Run Text="{Binding Channels.Count, FallbackValue=0, Mode=OneWay}" />
|
||||
<Run Text="channels selected" />
|
||||
</TextBlock>
|
||||
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:dialogs="clr-namespace:DiscordChatExporter.Gui.ViewModels.Dialogs"
|
||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||
Width="500">
|
||||
<Design.DataContext>
|
||||
<dialogs:MessageBoxViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
Width="500"
|
||||
x:DataType="dialogs:MessageBoxViewModel">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<!-- Title -->
|
||||
<TextBlock
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
|
||||
xmlns:dialogs="clr-namespace:DiscordChatExporter.Gui.ViewModels.Dialogs"
|
||||
Width="380">
|
||||
<Design.DataContext>
|
||||
<dialogs:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
Width="380"
|
||||
x:DataType="dialogs:SettingsViewModel">
|
||||
<Grid RowDefinitions="Auto,*,Auto">
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
@@ -24,6 +21,19 @@
|
||||
BorderThickness="0,1">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<!-- Theme -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Preferred user interface theme">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Theme" />
|
||||
<ComboBox
|
||||
Width="150"
|
||||
DockPanel.Dock="Right"
|
||||
ItemsSource="{Binding AvailableThemes}"
|
||||
SelectedItem="{Binding Theme}" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Auto-updates -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
@@ -35,19 +45,6 @@
|
||||
<ToggleSwitch DockPanel.Dock="Right" IsChecked="{Binding IsAutoUpdateEnabled}" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Dark mode -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
LastChildFill="False"
|
||||
ToolTip.Tip="Use darker colors in the UI">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Dark mode" />
|
||||
<ToggleSwitch
|
||||
x:Name="DarkModeToggleSwitch"
|
||||
DockPanel.Dock="Right"
|
||||
IsChecked="{Binding IsDarkModeEnabled}"
|
||||
IsCheckedChanged="DarkModeToggleSwitch_OnIsCheckedChanged" />
|
||||
</DockPanel>
|
||||
|
||||
<!-- Persist token -->
|
||||
<DockPanel
|
||||
Margin="16,8"
|
||||
@@ -66,7 +63,7 @@
|
||||
<ComboBox
|
||||
Width="150"
|
||||
DockPanel.Dock="Right"
|
||||
ItemsSource="{Binding AvailableThreadInclusions}"
|
||||
ItemsSource="{Binding AvailableThreadInclusionModes}"
|
||||
SelectedItem="{Binding ThreadInclusionMode}" />
|
||||
</DockPanel>
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Windows;
|
||||
using Avalonia.Interactivity;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.Framework;
|
||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||
|
||||
namespace DiscordChatExporter.Gui.Views.Dialogs;
|
||||
@@ -8,20 +6,4 @@ namespace DiscordChatExporter.Gui.Views.Dialogs;
|
||||
public partial class SettingsView : UserControl<SettingsViewModel>
|
||||
{
|
||||
public SettingsView() => InitializeComponent();
|
||||
|
||||
private void DarkModeToggleSwitch_OnIsCheckedChanged(object? sender, RoutedEventArgs args)
|
||||
{
|
||||
if (DarkModeToggleSwitch.IsChecked is true)
|
||||
{
|
||||
App.SetDarkTheme();
|
||||
}
|
||||
else if (DarkModeToggleSwitch.IsChecked is false)
|
||||
{
|
||||
App.SetLightTheme();
|
||||
}
|
||||
else
|
||||
{
|
||||
App.SetDefaultTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,10 @@
|
||||
Height="625"
|
||||
MinWidth="600"
|
||||
MinHeight="400"
|
||||
x:DataType="viewModels:MainViewModel"
|
||||
Icon="/favicon.ico"
|
||||
RenderOptions.BitmapInterpolationMode="HighQuality"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
<Design.DataContext>
|
||||
<viewModels:MainViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<dialogHostAvalonia:DialogHost
|
||||
x:Name="DialogHost"
|
||||
CloseOnClickAway="False"
|
||||
|
||||
19
Readme.md
19
Readme.md
@@ -23,8 +23,7 @@
|
||||
**DiscordChatExporter** is an application that can be used to export message history from any [Discord](https://discord.com) channel to a file.
|
||||
It works with direct messages, group messages, and server channels, and supports Discord's dialect of markdown as well as most other rich media features.
|
||||
|
||||
<!-- Can't use a relative link here due to a bug in markdown parsing -->
|
||||
> ❔ If you have questions or issues, **please refer to the [docs](https://github.com/Tyrrrz/DiscordChatExporter/tree/master/.docs)**.
|
||||
> ❔ If you have questions or issues, **please refer to the [docs](.docs)**.
|
||||
|
||||
> 💬 If you want to chat, **join my [Discord server](https://discord.gg/2SUWKFnHSm)**.
|
||||
|
||||
@@ -51,17 +50,8 @@ To learn more about the war and how you can help, [click here](https://tyrrrz.me
|
||||
- 📦 [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`
|
||||
|
||||
> **Important**:
|
||||
> To run **DiscordChatExporter**, you need to make sure that the **.NET 8.0 Runtime** is installed.
|
||||
> You can download it here:
|
||||
>
|
||||
> - [.NET 8.0 Runtime for **macOS x64**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-macos-x64-installer)
|
||||
> - [.NET 8.0 Runtime for **macOS arm64**](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-8.0.0-macos-arm64-installer)
|
||||
> - [.NET 8.0 Runtime for **Linux**](https://learn.microsoft.com/dotnet/core/install/linux) (find the correct download for your distro)
|
||||
> - On Windows, the runtime should be installed automatically when you run the application for the first time
|
||||
>
|
||||
> This should not be necessary if you install **DiscordChatExporter** using a package manager, as it should take care of the dependencies for you.
|
||||
> This is also not necessary if you are running **DiscordChatExporter** via Docker, because the image already contains the runtime.
|
||||
> **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.
|
||||
|
||||
> **Note**:
|
||||
> AUR and Nix packages linked above are maintained by the community.
|
||||
@@ -69,8 +59,7 @@ To learn more about the war and how you can help, [click here](https://tyrrrz.me
|
||||
|
||||
## Features
|
||||
|
||||
- Graphical and command-line interfaces
|
||||
- Fully cross-platform
|
||||
- Cross-platform graphical and command-line interfaces
|
||||
- Authentication via either a user or a bot token
|
||||
- Multiple output formats: HTML (dark/light), TXT, CSV, JSON
|
||||
- Support for markdown, attachments, embeds, emoji, and other rich media features
|
||||
|
||||
Reference in New Issue
Block a user