mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2024-11-25 05:42:45 +01:00
Compare commits
2 Commits
8aeb590bb3
...
65d935cca1
Author | SHA1 | Date | |
---|---|---|---|
65d935cca1 | |||
6e64c86d7a |
@ -9,6 +9,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<ApplicationIcon>./Resources/icon.ico</ApplicationIcon>
|
<ApplicationIcon>./Resources/icon.ico</ApplicationIcon>
|
||||||
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.CheckBox"
|
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.CheckBox"
|
||||||
mc:Ignorable="d" d:DesignWidth="500"
|
mc:Ignorable="d" d:DesignWidth="500"
|
||||||
x:Class="DHT.Desktop.Dialogs.CheckBox.CheckBoxDialog"
|
x:Class="DHT.Desktop.Dialogs.CheckBox.CheckBoxDialog"
|
||||||
|
x:DataType="namespace:CheckBoxDialogModel"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Width="500" SizeToContent="Height" CanResize="False"
|
Width="500" SizeToContent="Height" CanResize="False"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.Message"
|
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.Message"
|
||||||
mc:Ignorable="d" d:DesignWidth="500"
|
mc:Ignorable="d" d:DesignWidth="500"
|
||||||
x:Class="DHT.Desktop.Dialogs.Message.MessageDialog"
|
x:Class="DHT.Desktop.Dialogs.Message.MessageDialog"
|
||||||
|
x:DataType="namespace:MessageDialogModel"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Width="500" SizeToContent="Height" CanResize="False"
|
Width="500" SizeToContent="Height" CanResize="False"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.Progress"
|
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.Progress"
|
||||||
mc:Ignorable="d" d:DesignWidth="500"
|
mc:Ignorable="d" d:DesignWidth="500"
|
||||||
x:Class="DHT.Desktop.Dialogs.Progress.ProgressDialog"
|
x:Class="DHT.Desktop.Dialogs.Progress.ProgressDialog"
|
||||||
|
x:DataType="namespace:ProgressDialogModel"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Opened="OnOpened"
|
Opened="OnOpened"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.TextBox"
|
xmlns:namespace="clr-namespace:DHT.Desktop.Dialogs.TextBox"
|
||||||
mc:Ignorable="d" d:DesignWidth="500"
|
mc:Ignorable="d" d:DesignWidth="500"
|
||||||
x:Class="DHT.Desktop.Dialogs.TextBox.TextBoxDialog"
|
x:Class="DHT.Desktop.Dialogs.TextBox.TextBoxDialog"
|
||||||
|
x:DataType="namespace:TextBoxDialogModel"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Width="500" SizeToContent="Height" CanResize="False"
|
Width="500" SizeToContent="Height" CanResize="False"
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||||
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="295"
|
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="295"
|
||||||
x:Class="DHT.Desktop.Main.AboutWindow"
|
x:Class="DHT.Desktop.Main.AboutWindow"
|
||||||
|
x:DataType="main:AboutWindowModel"
|
||||||
Title="About Discord History Tracker"
|
Title="About Discord History Tracker"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Width="480" Height="295" CanResize="False"
|
Width="480" Height="295" CanResize="False"
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="DHT.Desktop.Main.Controls.AttachmentFilterPanel">
|
x:Class="DHT.Desktop.Main.Controls.AttachmentFilterPanel"
|
||||||
|
x:DataType="controls:AttachmentFilterPanelModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<controls:AttachmentFilterPanelModel />
|
<controls:AttachmentFilterPanelModel />
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="DHT.Desktop.Main.Controls.MessageFilterPanel">
|
x:Class="DHT.Desktop.Main.Controls.MessageFilterPanel"
|
||||||
|
x:DataType="controls:MessageFilterPanelModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<controls:MessageFilterPanelModel />
|
<controls:MessageFilterPanelModel />
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="DHT.Desktop.Main.Controls.ServerConfigurationPanel">
|
x:Class="DHT.Desktop.Main.Controls.ServerConfigurationPanel"
|
||||||
|
x:DataType="controls:ServerConfigurationPanelModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<controls:ServerConfigurationPanelModel />
|
<controls:ServerConfigurationPanelModel />
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:Class="DHT.Desktop.Main.Controls.StatusBar">
|
x:Class="DHT.Desktop.Main.Controls.StatusBar"
|
||||||
|
x:DataType="controls:StatusBarModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<controls:StatusBarModel />
|
<controls:StatusBarModel />
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.MainWindow"
|
x:Class="DHT.Desktop.Main.MainWindow"
|
||||||
|
x:DataType="main:MainWindowModel"
|
||||||
Title="{Binding Title}"
|
Title="{Binding Title}"
|
||||||
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
Width="800" Height="500"
|
Width="800" Height="500"
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.AdvancedPage">
|
x:Class="DHT.Desktop.Main.Pages.AdvancedPage"
|
||||||
|
x:DataType="pages:AdvancedPageModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:AdvancedPageModel />
|
<pages:AdvancedPageModel />
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.AttachmentsPage">
|
x:Class="DHT.Desktop.Main.Pages.AttachmentsPage"
|
||||||
|
x:DataType="pages:AttachmentsPageModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:AttachmentsPageModel />
|
<pages:AttachmentsPageModel />
|
||||||
@ -35,7 +36,7 @@
|
|||||||
<TextBlock Text="{Binding DownloadMessage}" Margin="10 0 0 0" VerticalAlignment="Center" DockPanel.Dock="Left" />
|
<TextBlock Text="{Binding DownloadMessage}" Margin="10 0 0 0" VerticalAlignment="Center" DockPanel.Dock="Left" />
|
||||||
<ProgressBar Value="{Binding DownloadProgress}" IsVisible="{Binding IsDownloading}" Margin="15 0" VerticalAlignment="Center" DockPanel.Dock="Right" />
|
<ProgressBar Value="{Binding DownloadProgress}" IsVisible="{Binding IsDownloading}" Margin="15 0" VerticalAlignment="Center" DockPanel.Dock="Right" />
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<controls:AttachmentFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !DataContext.IsDownloading, RelativeSource={RelativeSource AncestorType=UserControl}}" />
|
<controls:AttachmentFilterPanel DataContext="{Binding FilterModel}" IsEnabled="{Binding !IsDownloading, RelativeSource={RelativeSource AncestorType=pages:AttachmentsPageModel}}" />
|
||||||
<StackPanel Orientation="Vertical" Spacing="12">
|
<StackPanel Orientation="Vertical" Spacing="12">
|
||||||
<Expander Header="Download Status" IsExpanded="True">
|
<Expander Header="Download Status" IsExpanded="True">
|
||||||
<DataGrid ItemsSource="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
|
<DataGrid ItemsSource="{Binding StatisticsRows}" AutoGenerateColumns="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False" IsReadOnly="True">
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.DatabasePage">
|
x:Class="DHT.Desktop.Main.Pages.DatabasePage"
|
||||||
|
x:DataType="pages:DatabasePageModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:DatabasePageModel />
|
<pages:DatabasePageModel />
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.DebugPage">
|
x:Class="DHT.Desktop.Main.Pages.DebugPage"
|
||||||
|
x:DataType="pages:DebugPageModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:DebugPageModel />
|
<pages:DebugPageModel />
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.TrackingPage">
|
x:Class="DHT.Desktop.Main.Pages.TrackingPage"
|
||||||
|
x:DataType="pages:TrackingPageModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:TrackingPageModel />
|
<pages:TrackingPageModel />
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Pages.ViewerPage">
|
x:Class="DHT.Desktop.Main.Pages.ViewerPage"
|
||||||
|
x:DataType="pages:ViewerPageModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<pages:ViewerPageModel />
|
<pages:ViewerPageModel />
|
||||||
|
@ -35,7 +35,7 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
|
|||||||
set => Change(ref hasFilters, value);
|
set => Change(ref hasFilters, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageFilterPanelModel FilterModel { get; }
|
public MessageFilterPanelModel FilterModel { get; }
|
||||||
|
|
||||||
private readonly Window window;
|
private readonly Window window;
|
||||||
private readonly IDatabaseFile db;
|
private readonly IDatabaseFile db;
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
xmlns:screens="clr-namespace:DHT.Desktop.Main.Screens"
|
xmlns:screens="clr-namespace:DHT.Desktop.Main.Screens"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Screens.MainContentScreen">
|
x:Class="DHT.Desktop.Main.Screens.MainContentScreen"
|
||||||
|
x:DataType="screens:MainContentScreenModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<screens:MainContentScreenModel />
|
<screens:MainContentScreenModel />
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:screens="clr-namespace:DHT.Desktop.Main.Screens"
|
xmlns:screens="clr-namespace:DHT.Desktop.Main.Screens"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="DHT.Desktop.Main.Screens.WelcomeScreen">
|
x:Class="DHT.Desktop.Main.Screens.WelcomeScreen"
|
||||||
|
x:DataType="screens:WelcomeScreenModel">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<screens:WelcomeScreenModel />
|
<screens:WelcomeScreenModel />
|
||||||
|
3
app/Server/Database/Export/Snowflake.cs
Normal file
3
app/Server/Database/Export/Snowflake.cs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
readonly record struct Snowflake(ulong Id);
|
23
app/Server/Database/Export/SnowflakeJsonSerializer.cs
Normal file
23
app/Server/Database/Export/SnowflakeJsonSerializer.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
sealed class SnowflakeJsonSerializer : JsonConverter<Snowflake> {
|
||||||
|
public override Snowflake Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
return new Snowflake(ulong.Parse(reader.GetString()!));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) {
|
||||||
|
writer.WriteStringValue(value.Id.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Snowflake ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||||
|
return new Snowflake(ulong.Parse(reader.GetString()!));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteAsPropertyName(Utf8JsonWriter writer, Snowflake value, JsonSerializerOptions options) {
|
||||||
|
writer.WritePropertyName(value.Id.ToString());
|
||||||
|
}
|
||||||
|
}
|
93
app/Server/Database/Export/ViewerJson.cs
Normal file
93
app/Server/Database/Export/ViewerJson.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
sealed class ViewerJson {
|
||||||
|
public required JsonMeta Meta { get; init; }
|
||||||
|
public required Dictionary<Snowflake, Dictionary<Snowflake, JsonMessage>> Data { get; init; }
|
||||||
|
|
||||||
|
public sealed class JsonMeta {
|
||||||
|
public required Dictionary<Snowflake, JsonUser> Users { get; init; }
|
||||||
|
public required List<Snowflake> Userindex { get; init; }
|
||||||
|
public required List<JsonServer> Servers { get; init; }
|
||||||
|
public required Dictionary<Snowflake, JsonChannel> Channels { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonUser {
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Avatar { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Tag { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonServer {
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required string Type { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonChannel {
|
||||||
|
public required int Server { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Parent { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Position { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Topic { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public bool? Nsfw { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonMessage {
|
||||||
|
public required int U { get; init; }
|
||||||
|
public required long T { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? M { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public long? Te { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? R { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public JsonMessageAttachment[]? A { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string[]? E { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public JsonMessageReaction[]? Re { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonMessageAttachment {
|
||||||
|
public required string Url { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Width { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public int? Height { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonMessageReaction {
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? Id { get; init; }
|
||||||
|
|
||||||
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
|
public string? N { get; init; }
|
||||||
|
|
||||||
|
public required bool A { get; init; }
|
||||||
|
public required int C { get; init; }
|
||||||
|
}
|
||||||
|
}
|
11
app/Server/Database/Export/ViewerJsonContext.cs
Normal file
11
app/Server/Database/Export/ViewerJsonContext.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
[JsonSourceGenerationOptions(
|
||||||
|
Converters = new [] { typeof(SnowflakeJsonSerializer) },
|
||||||
|
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
|
||||||
|
GenerationMode = JsonSourceGenerationMode.Default
|
||||||
|
)]
|
||||||
|
[JsonSerializable(typeof(ViewerJson))]
|
||||||
|
sealed partial class ViewerJsonContext : JsonSerializerContext {}
|
@ -42,26 +42,28 @@ public static class ViewerJsonExport {
|
|||||||
|
|
||||||
perf.Step("Collect database data");
|
perf.Step("Collect database data");
|
||||||
|
|
||||||
var value = new {
|
var value = new ViewerJson {
|
||||||
meta = new { users, userindex, servers, channels },
|
Meta = new ViewerJson.JsonMeta {
|
||||||
data = GenerateMessageList(includedMessages, userIndices, strategy),
|
Users = users,
|
||||||
|
Userindex = userindex,
|
||||||
|
Servers = servers,
|
||||||
|
Channels = channels
|
||||||
|
},
|
||||||
|
Data = GenerateMessageList(includedMessages, userIndices, strategy)
|
||||||
};
|
};
|
||||||
|
|
||||||
perf.Step("Generate value object");
|
perf.Step("Generate value object");
|
||||||
|
|
||||||
var opts = new JsonSerializerOptions();
|
await JsonSerializer.SerializeAsync(stream, value, ViewerJsonContext.Default.ViewerJson);
|
||||||
opts.Converters.Add(new ViewerJsonSnowflakeSerializer());
|
|
||||||
|
|
||||||
await JsonSerializer.SerializeAsync(stream, value, opts);
|
|
||||||
|
|
||||||
perf.Step("Serialize to JSON");
|
perf.Step("Serialize to JSON");
|
||||||
perf.End();
|
perf.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<string> userindex, out Dictionary<ulong, object> userIndices) {
|
private static Dictionary<Snowflake, ViewerJson.JsonUser> GenerateUserList(IDatabaseFile db, HashSet<ulong> userIds, out List<Snowflake> userindex, out Dictionary<ulong, int> userIndices) {
|
||||||
var users = new Dictionary<string, object>();
|
var users = new Dictionary<Snowflake, ViewerJson.JsonUser>();
|
||||||
userindex = new List<string>();
|
userindex = new List<Snowflake>();
|
||||||
userIndices = new Dictionary<ulong, object>();
|
userIndices = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
foreach (var user in db.GetAllUsers()) {
|
foreach (var user in db.GetAllUsers()) {
|
||||||
var id = user.Id;
|
var id = user.Id;
|
||||||
@ -69,30 +71,23 @@ public static class ViewerJsonExport {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj = new Dictionary<string, object> {
|
var idSnowflake = new Snowflake(id);
|
||||||
["name"] = user.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
if (user.AvatarUrl != null) {
|
|
||||||
obj["avatar"] = user.AvatarUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.Discriminator != null) {
|
|
||||||
obj["tag"] = user.Discriminator;
|
|
||||||
}
|
|
||||||
|
|
||||||
var idStr = id.ToString();
|
|
||||||
userIndices[id] = users.Count;
|
userIndices[id] = users.Count;
|
||||||
userindex.Add(idStr);
|
userindex.Add(idSnowflake);
|
||||||
users[idStr] = obj;
|
|
||||||
|
users[idSnowflake] = new ViewerJson.JsonUser {
|
||||||
|
Name = user.Name,
|
||||||
|
Avatar = user.AvatarUrl,
|
||||||
|
Tag = user.Discriminator
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return users;
|
return users;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<object> GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, object> serverIndices) {
|
private static List<ViewerJson.JsonServer> GenerateServerList(IDatabaseFile db, HashSet<ulong> serverIds, out Dictionary<ulong, int> serverIndices) {
|
||||||
var servers = new List<object>();
|
var servers = new List<ViewerJson.JsonServer>();
|
||||||
serverIndices = new Dictionary<ulong, object>();
|
serverIndices = new Dictionary<ulong, int>();
|
||||||
|
|
||||||
foreach (var server in db.GetAllServers()) {
|
foreach (var server in db.GetAllServers()) {
|
||||||
var id = server.Id;
|
var id = server.Id;
|
||||||
@ -101,113 +96,78 @@ public static class ViewerJsonExport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverIndices[id] = servers.Count;
|
serverIndices[id] = servers.Count;
|
||||||
servers.Add(new Dictionary<string, object> {
|
|
||||||
["name"] = server.Name,
|
servers.Add(new ViewerJson.JsonServer {
|
||||||
["type"] = ServerTypes.ToJsonViewerString(server.Type),
|
Name = server.Name,
|
||||||
|
Type = ServerTypes.ToJsonViewerString(server.Type)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, object> GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, object> serverIndices) {
|
private static Dictionary<Snowflake, ViewerJson.JsonChannel> GenerateChannelList(List<Channel> includedChannels, Dictionary<ulong, int> serverIndices) {
|
||||||
var channels = new Dictionary<string, object>();
|
var channels = new Dictionary<Snowflake, ViewerJson.JsonChannel>();
|
||||||
|
|
||||||
foreach (var channel in includedChannels) {
|
foreach (var channel in includedChannels) {
|
||||||
var obj = new Dictionary<string, object> {
|
var channelIdSnowflake = new Snowflake(channel.Id);
|
||||||
["server"] = serverIndices[channel.Server],
|
|
||||||
["name"] = channel.Name,
|
channels[channelIdSnowflake] = new ViewerJson.JsonChannel {
|
||||||
|
Server = serverIndices[channel.Server],
|
||||||
|
Name = channel.Name,
|
||||||
|
Parent = channel.ParentId?.ToString(),
|
||||||
|
Position = channel.Position,
|
||||||
|
Topic = channel.Topic,
|
||||||
|
Nsfw = channel.Nsfw
|
||||||
};
|
};
|
||||||
|
|
||||||
if (channel.ParentId != null) {
|
|
||||||
obj["parent"] = channel.ParentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.Position != null) {
|
|
||||||
obj["position"] = channel.Position;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.Topic != null) {
|
|
||||||
obj["topic"] = channel.Topic;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channel.Nsfw != null) {
|
|
||||||
obj["nsfw"] = channel.Nsfw;
|
|
||||||
}
|
|
||||||
|
|
||||||
channels[channel.Id.ToString()] = obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, Dictionary<string, object>> GenerateMessageList( List<Message> includedMessages, Dictionary<ulong, object> userIndices, IViewerExportStrategy strategy) {
|
private static Dictionary<Snowflake, Dictionary<Snowflake, ViewerJson.JsonMessage>> GenerateMessageList(List<Message> includedMessages, Dictionary<ulong, int> userIndices, IViewerExportStrategy strategy) {
|
||||||
var data = new Dictionary<string, Dictionary<string, object>>();
|
var data = new Dictionary<Snowflake, Dictionary<Snowflake, ViewerJson.JsonMessage>>();
|
||||||
|
|
||||||
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
|
foreach (var grouping in includedMessages.GroupBy(static message => message.Channel)) {
|
||||||
var channel = grouping.Key.ToString();
|
var channelIdSnowflake = new Snowflake(grouping.Key);
|
||||||
var channelData = new Dictionary<string, object>();
|
var channelData = new Dictionary<Snowflake, ViewerJson.JsonMessage>();
|
||||||
|
|
||||||
foreach (var message in grouping) {
|
foreach (var message in grouping) {
|
||||||
var obj = new Dictionary<string, object> {
|
var messageIdSnowflake = new Snowflake(message.Id);
|
||||||
["u"] = userIndices[message.Sender],
|
|
||||||
["t"] = message.Timestamp,
|
channelData[messageIdSnowflake] = new ViewerJson.JsonMessage {
|
||||||
};
|
U = userIndices[message.Sender],
|
||||||
|
T = message.Timestamp,
|
||||||
if (!string.IsNullOrEmpty(message.Text)) {
|
M = string.IsNullOrEmpty(message.Text) ? null : message.Text,
|
||||||
obj["m"] = message.Text;
|
Te = message.EditTimestamp,
|
||||||
}
|
R = message.RepliedToId?.ToString(),
|
||||||
|
|
||||||
if (message.EditTimestamp != null) {
|
A = message.Attachments.IsEmpty ? null : message.Attachments.Select(attachment => {
|
||||||
obj["te"] = message.EditTimestamp;
|
var a = new ViewerJson.JsonMessageAttachment {
|
||||||
}
|
Url = strategy.GetAttachmentUrl(attachment),
|
||||||
|
Name = Uri.TryCreate(attachment.NormalizedUrl, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.LocalPath) : attachment.NormalizedUrl
|
||||||
if (message.RepliedToId != null) {
|
|
||||||
obj["r"] = message.RepliedToId.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!message.Attachments.IsEmpty) {
|
|
||||||
obj["a"] = message.Attachments.Select(attachment => {
|
|
||||||
var a = new Dictionary<string, object> {
|
|
||||||
{ "url", strategy.GetAttachmentUrl(attachment) },
|
|
||||||
{ "name", Uri.TryCreate(attachment.NormalizedUrl, UriKind.Absolute, out var uri) ? Path.GetFileName(uri.LocalPath) : attachment.NormalizedUrl },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (attachment is { Width: not null, Height: not null }) {
|
if (attachment is { Width: not null, Height: not null }) {
|
||||||
a["width"] = attachment.Width;
|
a.Width = attachment.Width;
|
||||||
a["height"] = attachment.Height;
|
a.Height = attachment.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
return a;
|
return a;
|
||||||
}).ToArray();
|
}).ToArray(),
|
||||||
}
|
|
||||||
|
E = message.Embeds.IsEmpty ? null : message.Embeds.Select(static embed => embed.Json).ToArray(),
|
||||||
if (!message.Embeds.IsEmpty) {
|
|
||||||
obj["e"] = message.Embeds.Select(static embed => embed.Json).ToArray();
|
Re = message.Reactions.IsEmpty ? null : message.Reactions.Select(static reaction => new ViewerJson.JsonMessageReaction {
|
||||||
}
|
Id = reaction.EmojiId?.ToString(),
|
||||||
|
N = reaction.EmojiName,
|
||||||
if (!message.Reactions.IsEmpty) {
|
A = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated),
|
||||||
obj["re"] = message.Reactions.Select(static reaction => {
|
C = reaction.Count
|
||||||
var r = new Dictionary<string, object>();
|
}).ToArray()
|
||||||
|
};
|
||||||
if (reaction.EmojiId != null) {
|
|
||||||
r["id"] = reaction.EmojiId.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reaction.EmojiName != null) {
|
|
||||||
r["n"] = reaction.EmojiName;
|
|
||||||
}
|
|
||||||
|
|
||||||
r["a"] = reaction.EmojiFlags.HasFlag(EmojiFlags.Animated);
|
|
||||||
r["c"] = reaction.Count;
|
|
||||||
return r;
|
|
||||||
}).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
channelData[message.Id.ToString()] = obj;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data[channel] = channelData;
|
data[channelIdSnowflake] = channelData;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace DHT.Server.Database.Export;
|
|
||||||
|
|
||||||
sealed class ViewerJsonSnowflakeSerializer : JsonConverter<ulong> {
|
|
||||||
public override ulong Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
|
||||||
return ulong.Parse(reader.GetString()!);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, ulong value, JsonSerializerOptions options) {
|
|
||||||
writer.WriteStringValue(value.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user