mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-10 16:01:55 +02:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 705e60c76d | |||
| 8ac8c16db3 | |||
| 1850caba71 | |||
| 6ccaf3f106 | |||
| d7345e91d3 | |||
| e5e9d4c9ff | |||
| b280f8b736 | |||
| ca452d2421 | |||
| 9e7d4033e0 | |||
| 263a606bff | |||
| 1e853f11b0 | |||
| 77d0956f5d | |||
| d251e8ecf6 | |||
| 01f0e6952d | |||
| 28a67ab60d | |||
| 42bb4e72d2 | |||
| a79d72f975 | |||
| b110413c08 | |||
| a556926fe6 | |||
| 5a57b4a6b1 |
@@ -11,31 +11,33 @@ namespace DiscordChatExporter
|
|||||||
{
|
{
|
||||||
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
||||||
|
ServiceLocator.Current.GetInstance<ISettingsService>().Load();
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
SimpleIoc.Default.Register<IDataService, DataService>();
|
SimpleIoc.Default.Register<IDataService, DataService>();
|
||||||
SimpleIoc.Default.Register<IExportService, ExportService>();
|
SimpleIoc.Default.Register<IExportService, ExportService>();
|
||||||
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
|
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
|
||||||
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
|
||||||
|
|
||||||
// View models
|
// View models
|
||||||
SimpleIoc.Default.Register<IErrorViewModel, ErrorViewModel>(true);
|
SimpleIoc.Default.Register<IErrorViewModel, ErrorViewModel>(true);
|
||||||
|
SimpleIoc.Default.Register<IExportDoneViewModel, ExportDoneViewModel>(true);
|
||||||
|
SimpleIoc.Default.Register<IExportSetupViewModel, ExportSetupViewModel>(true);
|
||||||
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>(true);
|
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>(true);
|
||||||
SimpleIoc.Default.Register<ISettingsViewModel, SettingsViewModel>(true);
|
SimpleIoc.Default.Register<ISettingsViewModel, SettingsViewModel>(true);
|
||||||
SimpleIoc.Default.Register<IExportDoneViewModel, ExportDoneViewModel>(true);
|
|
||||||
|
|
||||||
// Load settings
|
|
||||||
ServiceLocator.Current.GetInstance<ISettingsService>().Load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Cleanup()
|
public static void Cleanup()
|
||||||
{
|
{
|
||||||
// Save settings
|
// Settings
|
||||||
ServiceLocator.Current.GetInstance<ISettingsService>().Save();
|
ServiceLocator.Current.GetInstance<ISettingsService>().Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IErrorViewModel ErrorViewModel => ServiceLocator.Current.GetInstance<IErrorViewModel>();
|
public IErrorViewModel ErrorViewModel => ServiceLocator.Current.GetInstance<IErrorViewModel>();
|
||||||
|
public IExportDoneViewModel ExportDoneViewModel => ServiceLocator.Current.GetInstance<IExportDoneViewModel>();
|
||||||
|
public IExportSetupViewModel ExportSetupViewModel => ServiceLocator.Current.GetInstance<IExportSetupViewModel>();
|
||||||
public IMainViewModel MainViewModel => ServiceLocator.Current.GetInstance<IMainViewModel>();
|
public IMainViewModel MainViewModel => ServiceLocator.Current.GetInstance<IMainViewModel>();
|
||||||
public ISettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance<ISettingsViewModel>();
|
public ISettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance<ISettingsViewModel>();
|
||||||
public IExportDoneViewModel ExportDoneViewModel => ServiceLocator.Current.GetInstance<IExportDoneViewModel>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,9 +48,6 @@
|
|||||||
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
|
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.3.0.19032, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
|
<HintPath>..\packages\MvvmLightLibs.5.3.0.0\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="HtmlAgilityPack, Version=1.5.5.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\HtmlAgilityPack.1.5.5\lib\Net45\HtmlAgilityPack.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="MaterialDesignColors, Version=1.1.3.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MaterialDesignColors, Version=1.1.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<HintPath>..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll</HintPath>
|
<HintPath>..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
@@ -84,17 +81,23 @@
|
|||||||
<Reference Include="PresentationFramework" />
|
<Reference Include="PresentationFramework" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Exceptions\UnathorizedException.cs" />
|
<Compile Include="Exceptions\HttpErrorStatusCodeException.cs" />
|
||||||
<Compile Include="Messages\ShowErrorMessage.cs" />
|
<Compile Include="Messages\ShowErrorMessage.cs" />
|
||||||
<Compile Include="Messages\ShowExportDoneMessage.cs" />
|
<Compile Include="Messages\ShowExportDoneMessage.cs" />
|
||||||
|
<Compile Include="Messages\ShowExportSetupMessage.cs" />
|
||||||
<Compile Include="Messages\ShowSettingsMessage.cs" />
|
<Compile Include="Messages\ShowSettingsMessage.cs" />
|
||||||
|
<Compile Include="Messages\StartExportMessage.cs" />
|
||||||
<Compile Include="Models\AttachmentType.cs" />
|
<Compile Include="Models\AttachmentType.cs" />
|
||||||
<Compile Include="Models\ChannelChatLog.cs" />
|
<Compile Include="Models\ChannelChatLog.cs" />
|
||||||
<Compile Include="Models\ChannelType.cs" />
|
<Compile Include="Models\ChannelType.cs" />
|
||||||
|
<Compile Include="Models\ExportFormat.cs" />
|
||||||
|
<Compile Include="Models\Extensions.cs" />
|
||||||
<Compile Include="Services\IMessageGroupService.cs" />
|
<Compile Include="Services\IMessageGroupService.cs" />
|
||||||
<Compile Include="Services\MessageGroupService.cs" />
|
<Compile Include="Services\MessageGroupService.cs" />
|
||||||
<Compile Include="ViewModels\ErrorViewModel.cs" />
|
<Compile Include="ViewModels\ErrorViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\ExportSetupViewModel.cs" />
|
||||||
<Compile Include="ViewModels\IErrorViewModel.cs" />
|
<Compile Include="ViewModels\IErrorViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\IExportSetupViewModel.cs" />
|
||||||
<Compile Include="ViewModels\ISettingsViewModel.cs" />
|
<Compile Include="ViewModels\ISettingsViewModel.cs" />
|
||||||
<Compile Include="ViewModels\IExportDoneViewModel.cs" />
|
<Compile Include="ViewModels\IExportDoneViewModel.cs" />
|
||||||
<Compile Include="ViewModels\SettingsViewModel.cs" />
|
<Compile Include="ViewModels\SettingsViewModel.cs" />
|
||||||
@@ -105,6 +108,9 @@
|
|||||||
<Compile Include="Views\ExportDoneDialog.ammy.cs">
|
<Compile Include="Views\ExportDoneDialog.ammy.cs">
|
||||||
<DependentUpon>ExportDoneDialog.ammy</DependentUpon>
|
<DependentUpon>ExportDoneDialog.ammy</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Views\ExportSetupDialog.ammy.cs">
|
||||||
|
<DependentUpon>ExportSetupDialog.ammy</DependentUpon>
|
||||||
|
</Compile>
|
||||||
<Compile Include="Views\SettingsDialog.ammy.cs">
|
<Compile Include="Views\SettingsDialog.ammy.cs">
|
||||||
<DependentUpon>SettingsDialog.ammy</DependentUpon>
|
<DependentUpon>SettingsDialog.ammy</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
@@ -123,6 +129,11 @@
|
|||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
<DependentUpon>ExportDoneDialog.ammy</DependentUpon>
|
<DependentUpon>ExportDoneDialog.ammy</DependentUpon>
|
||||||
</Page>
|
</Page>
|
||||||
|
<Page Include="Views\ExportSetupDialog.g.xaml">
|
||||||
|
<SubType>Designer</SubType>
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
<DependentUpon>ExportSetupDialog.ammy</DependentUpon>
|
||||||
|
</Page>
|
||||||
<Page Include="Views\MainWindow.g.xaml">
|
<Page Include="Views\MainWindow.g.xaml">
|
||||||
<SubType>Designer</SubType>
|
<SubType>Designer</SubType>
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
@@ -137,7 +148,6 @@
|
|||||||
<Compile Include="Models\Guild.cs" />
|
<Compile Include="Models\Guild.cs" />
|
||||||
<Compile Include="Models\Message.cs" />
|
<Compile Include="Models\Message.cs" />
|
||||||
<Compile Include="Models\MessageGroup.cs" />
|
<Compile Include="Models\MessageGroup.cs" />
|
||||||
<Compile Include="Models\Theme.cs" />
|
|
||||||
<Compile Include="Models\User.cs" />
|
<Compile Include="Models\User.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
<Compile Include="Services\DataService.cs" />
|
<Compile Include="Services\DataService.cs" />
|
||||||
@@ -177,6 +187,7 @@
|
|||||||
</None>
|
</None>
|
||||||
<None Include="Views\ErrorDialog.ammy" />
|
<None Include="Views\ErrorDialog.ammy" />
|
||||||
<None Include="Views\ExportDoneDialog.ammy" />
|
<None Include="Views\ExportDoneDialog.ammy" />
|
||||||
|
<None Include="Views\ExportSetupDialog.ammy" />
|
||||||
<None Include="Views\MainWindow.ammy" />
|
<None Include="Views\MainWindow.ammy" />
|
||||||
<None Include="Views\SettingsDialog.ammy" />
|
<None Include="Views\SettingsDialog.ammy" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -186,7 +197,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="Resources\ExportService\DarkTheme.css" />
|
<EmbeddedResource Include="Resources\ExportService\DarkTheme.css" />
|
||||||
<EmbeddedResource Include="Resources\ExportService\LightTheme.css" />
|
<EmbeddedResource Include="Resources\ExportService\LightTheme.css" />
|
||||||
<EmbeddedResource Include="Resources\ExportService\Template.html" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
<Import Project="..\packages\Ammy.1.2.87\build\Ammy.targets" Condition="Exists('..\packages\Ammy.1.2.87\build\Ammy.targets')" />
|
<Import Project="..\packages\Ammy.1.2.87\build\Ammy.targets" Condition="Exists('..\packages\Ammy.1.2.87\build\Ammy.targets')" />
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Exceptions
|
||||||
|
{
|
||||||
|
public class HttpErrorStatusCodeException : Exception
|
||||||
|
{
|
||||||
|
public HttpStatusCode StatusCode { get; }
|
||||||
|
|
||||||
|
public HttpErrorStatusCodeException(HttpStatusCode statusCode)
|
||||||
|
{
|
||||||
|
StatusCode = statusCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Exceptions
|
|
||||||
{
|
|
||||||
public class UnathorizedException : Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using DiscordChatExporter.Models;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Messages
|
||||||
|
{
|
||||||
|
public class ShowExportSetupMessage
|
||||||
|
{
|
||||||
|
public Guild Guild { get; }
|
||||||
|
|
||||||
|
public Channel Channel { get; }
|
||||||
|
|
||||||
|
public ShowExportSetupMessage(Guild guild, Channel channel)
|
||||||
|
{
|
||||||
|
Guild = guild;
|
||||||
|
Channel = channel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using DiscordChatExporter.Models;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Messages
|
||||||
|
{
|
||||||
|
public class StartExportMessage
|
||||||
|
{
|
||||||
|
public Channel Channel { get; }
|
||||||
|
|
||||||
|
public string FilePath { get; }
|
||||||
|
|
||||||
|
public ExportFormat Format { get; }
|
||||||
|
|
||||||
|
public DateTime? From { get; }
|
||||||
|
|
||||||
|
public DateTime? To { get; }
|
||||||
|
|
||||||
|
public StartExportMessage(Channel channel, string filePath, ExportFormat format,
|
||||||
|
DateTime? from, DateTime? to)
|
||||||
|
{
|
||||||
|
Channel = channel;
|
||||||
|
FilePath = filePath;
|
||||||
|
Format = format;
|
||||||
|
From = from;
|
||||||
|
To = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
public enum AttachmentType
|
public enum AttachmentType
|
||||||
{
|
{
|
||||||
Unrecognized,
|
Other,
|
||||||
Image
|
Image
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Models
|
namespace DiscordChatExporter.Models
|
||||||
{
|
{
|
||||||
@@ -11,11 +10,15 @@ namespace DiscordChatExporter.Models
|
|||||||
|
|
||||||
public IReadOnlyList<MessageGroup> MessageGroups { get; }
|
public IReadOnlyList<MessageGroup> MessageGroups { get; }
|
||||||
|
|
||||||
public ChannelChatLog(Guild guild, Channel channel, IEnumerable<MessageGroup> messageGroups)
|
public int TotalMessageCount { get; }
|
||||||
|
|
||||||
|
public ChannelChatLog(Guild guild, Channel channel, IReadOnlyList<MessageGroup> messageGroups,
|
||||||
|
int totalMessageCount)
|
||||||
{
|
{
|
||||||
Guild = guild;
|
Guild = guild;
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
MessageGroups = messageGroups.ToArray();
|
MessageGroups = messageGroups;
|
||||||
|
TotalMessageCount = totalMessageCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace DiscordChatExporter.Models
|
||||||
|
{
|
||||||
|
public enum ExportFormat
|
||||||
|
{
|
||||||
|
PlainText,
|
||||||
|
HtmlDark,
|
||||||
|
HtmlLight
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Models
|
||||||
|
{
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
public static string GetFileExtension(this ExportFormat format)
|
||||||
|
{
|
||||||
|
if (format == ExportFormat.PlainText)
|
||||||
|
return "txt";
|
||||||
|
if (format == ExportFormat.HtmlDark)
|
||||||
|
return "html";
|
||||||
|
if (format == ExportFormat.HtmlLight)
|
||||||
|
return "html";
|
||||||
|
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetDisplayName(this ExportFormat format)
|
||||||
|
{
|
||||||
|
if (format == ExportFormat.PlainText)
|
||||||
|
return "Plain Text";
|
||||||
|
if (format == ExportFormat.HtmlDark)
|
||||||
|
return "HTML (Dark)";
|
||||||
|
if (format == ExportFormat.HtmlLight)
|
||||||
|
return "HTML (Light)";
|
||||||
|
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Models
|
namespace DiscordChatExporter.Models
|
||||||
{
|
{
|
||||||
@@ -20,14 +19,14 @@ namespace DiscordChatExporter.Models
|
|||||||
|
|
||||||
public Message(string id, User author,
|
public Message(string id, User author,
|
||||||
DateTime timeStamp, DateTime? editedTimeStamp,
|
DateTime timeStamp, DateTime? editedTimeStamp,
|
||||||
string content, IEnumerable<Attachment> attachments)
|
string content, IReadOnlyList<Attachment> attachments)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Author = author;
|
Author = author;
|
||||||
TimeStamp = timeStamp;
|
TimeStamp = timeStamp;
|
||||||
EditedTimeStamp = editedTimeStamp;
|
EditedTimeStamp = editedTimeStamp;
|
||||||
Content = content;
|
Content = content;
|
||||||
Attachments = attachments.ToArray();
|
Attachments = attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Models
|
namespace DiscordChatExporter.Models
|
||||||
{
|
{
|
||||||
@@ -12,11 +11,11 @@ namespace DiscordChatExporter.Models
|
|||||||
|
|
||||||
public IReadOnlyList<Message> Messages { get; }
|
public IReadOnlyList<Message> Messages { get; }
|
||||||
|
|
||||||
public MessageGroup(User author, DateTime timeStamp, IEnumerable<Message> messages)
|
public MessageGroup(User author, DateTime timeStamp, IReadOnlyList<Message> messages)
|
||||||
{
|
{
|
||||||
Author = author;
|
Author = author;
|
||||||
TimeStamp = timeStamp;
|
TimeStamp = timeStamp;
|
||||||
Messages = messages.ToArray();
|
Messages = messages;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
namespace DiscordChatExporter.Models
|
|
||||||
{
|
|
||||||
public enum Theme
|
|
||||||
{
|
|
||||||
Dark,
|
|
||||||
Light
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Resources;
|
||||||
using AmmySidekick;
|
using AmmySidekick;
|
||||||
|
|
||||||
namespace DiscordChatExporter
|
namespace DiscordChatExporter
|
||||||
@@ -15,5 +18,19 @@ namespace DiscordChatExporter
|
|||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetResourceString(string resourcePath)
|
||||||
|
{
|
||||||
|
var assembly = Assembly.GetExecutingAssembly();
|
||||||
|
var stream = assembly.GetManifestResourceStream(resourcePath);
|
||||||
|
if (stream == null)
|
||||||
|
throw new MissingManifestResourceException("Could not find resource");
|
||||||
|
|
||||||
|
using (stream)
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
return reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
[assembly: AssemblyTitle("DiscordChatExporter")]
|
[assembly: AssemblyTitle("DiscordChatExporter")]
|
||||||
[assembly: AssemblyCompany("Tyrrrz")]
|
[assembly: AssemblyCompany("Tyrrrz")]
|
||||||
[assembly: AssemblyCopyright("Copyright (c) 2017 Alexey Golub")]
|
[assembly: AssemblyCopyright("Copyright (c) 2017 Alexey Golub")]
|
||||||
[assembly: AssemblyVersion("2.0.0.0")]
|
[assembly: AssemblyVersion("2.2.1")]
|
||||||
[assembly: AssemblyFileVersion("2.0.0.0")]
|
[assembly: AssemblyFileVersion("2.2.1")]
|
||||||
@@ -12,11 +12,21 @@ a {
|
|||||||
|
|
||||||
a:hover { text-decoration: underline; }
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
div.pre, span.pre {
|
div.pre {
|
||||||
|
background-color: #2F3136;
|
||||||
|
color: rgb(131, 148, 150);
|
||||||
|
font-family: Consolas, Courier New, Courier, Monospace;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.pre {
|
||||||
background-color: #2F3136;
|
background-color: #2F3136;
|
||||||
font-family: Consolas, Courier New, Courier, Monospace;
|
font-family: Consolas, Courier New, Courier, Monospace;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
|
white-space: pre;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#info {
|
div#info {
|
||||||
|
|||||||
@@ -12,7 +12,15 @@ a {
|
|||||||
|
|
||||||
a:hover { text-decoration: underline; }
|
a:hover { text-decoration: underline; }
|
||||||
|
|
||||||
div.pre, span.pre {
|
div.pre {
|
||||||
|
background-color: #F9F9F9;
|
||||||
|
color: rgb(101, 123, 131);
|
||||||
|
font-family: Consolas, Courier New, Courier, Monospace;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.pre {
|
||||||
background-color: #F9F9F9;
|
background-color: #F9F9F9;
|
||||||
font-family: Consolas, Courier New, Courier, Monospace;
|
font-family: Consolas, Courier New, Courier, Monospace;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
<!-- This chat log was automatically generated by DiscordChatExporter (https://github.com/Tyrrrz/DiscordChatExporter) -->
|
|
||||||
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Discord Chat Log</title>
|
|
||||||
|
|
||||||
<meta charset="utf-8"/>
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
|
|
||||||
<style id="theme"></style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="info"></div>
|
|
||||||
<div id="log"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Exceptions;
|
using DiscordChatExporter.Exceptions;
|
||||||
@@ -21,16 +20,16 @@ namespace DiscordChatExporter.Services
|
|||||||
using (var response = await _httpClient.GetAsync(url))
|
using (var response = await _httpClient.GetAsync(url))
|
||||||
{
|
{
|
||||||
// Check status code
|
// Check status code
|
||||||
if (response.StatusCode.IsEither(HttpStatusCode.Unauthorized, HttpStatusCode.Forbidden))
|
// We throw our own exception here because default one doesn't have status code
|
||||||
throw new UnathorizedException();
|
if (!response.IsSuccessStatusCode)
|
||||||
response.EnsureSuccessStatusCode();
|
throw new HttpErrorStatusCodeException(response.StatusCode);
|
||||||
|
|
||||||
// Get content
|
// Get content
|
||||||
return await response.Content.ReadAsStringAsync();
|
return await response.Content.ReadAsStringAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Guild>> GetGuildsAsync(string token)
|
public async Task<IReadOnlyList<Guild>> GetGuildsAsync(string token)
|
||||||
{
|
{
|
||||||
// Form request url
|
// Form request url
|
||||||
var url = $"{ApiRoot}/users/@me/guilds?token={token}&limit=100";
|
var url = $"{ApiRoot}/users/@me/guilds?token={token}&limit=100";
|
||||||
@@ -39,12 +38,12 @@ namespace DiscordChatExporter.Services
|
|||||||
var content = await GetStringAsync(url);
|
var content = await GetStringAsync(url);
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
var guilds = JArray.Parse(content).Select(ParseGuild);
|
var guilds = JArray.Parse(content).Select(ParseGuild).ToArray();
|
||||||
|
|
||||||
return guilds;
|
return guilds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Channel>> GetDirectMessageChannelsAsync(string token)
|
public async Task<IReadOnlyList<Channel>> GetDirectMessageChannelsAsync(string token)
|
||||||
{
|
{
|
||||||
// Form request url
|
// Form request url
|
||||||
var url = $"{ApiRoot}/users/@me/channels?token={token}";
|
var url = $"{ApiRoot}/users/@me/channels?token={token}";
|
||||||
@@ -53,12 +52,12 @@ namespace DiscordChatExporter.Services
|
|||||||
var content = await GetStringAsync(url);
|
var content = await GetStringAsync(url);
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
var channels = JArray.Parse(content).Select(ParseChannel);
|
var channels = JArray.Parse(content).Select(ParseChannel).ToArray();
|
||||||
|
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Channel>> GetGuildChannelsAsync(string token, string guildId)
|
public async Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(string token, string guildId)
|
||||||
{
|
{
|
||||||
// Form request url
|
// Form request url
|
||||||
var url = $"{ApiRoot}/guilds/{guildId}/channels?token={token}";
|
var url = $"{ApiRoot}/guilds/{guildId}/channels?token={token}";
|
||||||
@@ -67,12 +66,13 @@ namespace DiscordChatExporter.Services
|
|||||||
var content = await GetStringAsync(url);
|
var content = await GetStringAsync(url);
|
||||||
|
|
||||||
// Parse
|
// Parse
|
||||||
var channels = JArray.Parse(content).Select(ParseChannel);
|
var channels = JArray.Parse(content).Select(ParseChannel).ToArray();
|
||||||
|
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<Message>> GetChannelMessagesAsync(string token, string channelId)
|
public async Task<IReadOnlyList<Message>> GetChannelMessagesAsync(string token, string channelId,
|
||||||
|
DateTime? from, DateTime? to)
|
||||||
{
|
{
|
||||||
var result = new List<Message>();
|
var result = new List<Message>();
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ namespace DiscordChatExporter.Services
|
|||||||
var messages = JArray.Parse(content).Select(ParseMessage);
|
var messages = JArray.Parse(content).Select(ParseMessage);
|
||||||
|
|
||||||
// Add messages to list
|
// Add messages to list
|
||||||
string currentMessageId = null;
|
var currentMessageId = default(string);
|
||||||
foreach (var message in messages)
|
foreach (var message in messages)
|
||||||
{
|
{
|
||||||
result.Add(message);
|
result.Add(message);
|
||||||
@@ -101,7 +101,12 @@ namespace DiscordChatExporter.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no messages - break
|
// If no messages - break
|
||||||
if (currentMessageId == null) break;
|
if (currentMessageId == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// If last message is older than from date - break
|
||||||
|
if (from != null && result.Last().TimeStamp < from)
|
||||||
|
break;
|
||||||
|
|
||||||
// Otherwise offset the next request
|
// Otherwise offset the next request
|
||||||
beforeId = currentMessageId;
|
beforeId = currentMessageId;
|
||||||
@@ -110,6 +115,12 @@ namespace DiscordChatExporter.Services
|
|||||||
// Messages appear newest first, we need to reverse
|
// Messages appear newest first, we need to reverse
|
||||||
result.Reverse();
|
result.Reverse();
|
||||||
|
|
||||||
|
// Filter
|
||||||
|
if (from != null)
|
||||||
|
result.RemoveAll(m => m.TimeStamp < from);
|
||||||
|
if (to != null)
|
||||||
|
result.RemoveAll(m => m.TimeStamp > to);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +204,7 @@ namespace DiscordChatExporter.Services
|
|||||||
var attachmentUrl = attachmentJson.Value<string>("url");
|
var attachmentUrl = attachmentJson.Value<string>("url");
|
||||||
var attachmentType = attachmentJson["width"] != null
|
var attachmentType = attachmentJson["width"] != null
|
||||||
? AttachmentType.Image
|
? AttachmentType.Image
|
||||||
: AttachmentType.Unrecognized;
|
: AttachmentType.Other;
|
||||||
var attachmentFileName = attachmentJson.Value<string>("filename");
|
var attachmentFileName = attachmentJson.Value<string>("filename");
|
||||||
var attachmentFileSize = attachmentJson.Value<long>("size");
|
var attachmentFileSize = attachmentJson.Value<long>("size");
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using System.IO;
|
using System;
|
||||||
using System.Linq;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Net;
|
||||||
using System.Resources;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Models;
|
using DiscordChatExporter.Models;
|
||||||
using HtmlAgilityPack;
|
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Services
|
namespace DiscordChatExporter.Services
|
||||||
@@ -19,80 +18,120 @@ namespace DiscordChatExporter.Services
|
|||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task ExportAsync(string filePath, ChannelChatLog log, Theme theme)
|
private async Task ExportAsTextAsync(string filePath, ChannelChatLog log)
|
||||||
{
|
{
|
||||||
return Task.Run(() =>
|
using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128 * 1024))
|
||||||
{
|
{
|
||||||
var doc = GetTemplate();
|
// Generation info
|
||||||
var style = GetStyle(theme);
|
await writer.WriteLineAsync("https://github.com/Tyrrrz/DiscordChatExporter");
|
||||||
var dateFormat = _settingsService.DateFormat;
|
await writer.WriteLineAsync();
|
||||||
|
|
||||||
// Set theme
|
// Guild and channel info
|
||||||
var themeHtml = doc.GetElementbyId("theme");
|
await writer.WriteLineAsync('='.Repeat(48));
|
||||||
themeHtml.InnerHtml = style;
|
await writer.WriteLineAsync($"Guild: {log.Guild}");
|
||||||
|
await writer.WriteLineAsync($"Channel: {log.Channel}");
|
||||||
|
await writer.WriteLineAsync($"Messages: {log.TotalMessageCount:N0}");
|
||||||
|
await writer.WriteLineAsync('='.Repeat(48));
|
||||||
|
await writer.WriteLineAsync();
|
||||||
|
|
||||||
// Title
|
// Chat log
|
||||||
var titleHtml = doc.DocumentNode.Element("html").Element("head").Element("title");
|
foreach (var group in log.MessageGroups)
|
||||||
titleHtml.InnerHtml = $"{log.Guild.Name} - {log.Channel.Name}";
|
|
||||||
|
|
||||||
// Info
|
|
||||||
var infoHtml = doc.GetElementbyId("info");
|
|
||||||
var infoLeftHtml = infoHtml.AppendChild(HtmlNode.CreateNode("<div class=\"info-left\"></div>"));
|
|
||||||
infoLeftHtml.AppendChild(HtmlNode.CreateNode(
|
|
||||||
$"<img class=\"guild-icon\" src=\"{log.Guild.IconUrl}\" />"));
|
|
||||||
var infoRightHtml = infoHtml.AppendChild(HtmlNode.CreateNode("<div class=\"info-right\"></div>"));
|
|
||||||
infoRightHtml.AppendChild(HtmlNode.CreateNode(
|
|
||||||
$"<div class=\"guild-name\">{log.Guild.Name}</div>"));
|
|
||||||
infoRightHtml.AppendChild(HtmlNode.CreateNode(
|
|
||||||
$"<div class=\"channel-name\">{log.Channel.Name}</div>"));
|
|
||||||
infoRightHtml.AppendChild(HtmlNode.CreateNode(
|
|
||||||
$"<div class=\"misc\">{log.MessageGroups.SelectMany(g => g.Messages).Count():N0} messages</div>"));
|
|
||||||
|
|
||||||
// Log
|
|
||||||
var logHtml = doc.GetElementbyId("log");
|
|
||||||
foreach (var messageGroup in log.MessageGroups)
|
|
||||||
{
|
{
|
||||||
// Container
|
var timeStampFormatted = group.TimeStamp.ToString(_settingsService.DateFormat);
|
||||||
var messageHtml = logHtml.AppendChild(HtmlNode.CreateNode("<div class=\"msg\"></div>"));
|
await writer.WriteLineAsync($"{group.Author} [{timeStampFormatted}]");
|
||||||
|
|
||||||
// Left
|
// Messages
|
||||||
var messageLeftHtml =
|
foreach (var message in group.Messages)
|
||||||
messageHtml.AppendChild(HtmlNode.CreateNode("<div class=\"msg-left\"></div>"));
|
|
||||||
|
|
||||||
// Avatar
|
|
||||||
messageLeftHtml.AppendChild(
|
|
||||||
HtmlNode.CreateNode($"<img class=\"msg-avatar\" src=\"{messageGroup.Author.AvatarUrl}\" />"));
|
|
||||||
|
|
||||||
// Right
|
|
||||||
var messageRightHtml =
|
|
||||||
messageHtml.AppendChild(HtmlNode.CreateNode("<div class=\"msg-right\"></div>"));
|
|
||||||
|
|
||||||
// Author
|
|
||||||
var authorName = HtmlDocument.HtmlEncode(messageGroup.Author.Name);
|
|
||||||
messageRightHtml.AppendChild(HtmlNode.CreateNode($"<span class=\"msg-user\">{authorName}</span>"));
|
|
||||||
|
|
||||||
// Date
|
|
||||||
var timeStamp = HtmlDocument.HtmlEncode(messageGroup.TimeStamp.ToString(dateFormat));
|
|
||||||
messageRightHtml.AppendChild(HtmlNode.CreateNode($"<span class=\"msg-date\">{timeStamp}</span>"));
|
|
||||||
|
|
||||||
// Individual messages
|
|
||||||
foreach (var message in messageGroup.Messages)
|
|
||||||
{
|
{
|
||||||
// Content
|
// Content
|
||||||
if (message.Content.IsNotBlank())
|
if (message.Content.IsNotBlank())
|
||||||
{
|
{
|
||||||
var content = FormatMessageContent(message.Content);
|
var contentFormatted = message.Content.Replace("\n", Environment.NewLine);
|
||||||
var contentHtml =
|
await writer.WriteLineAsync(contentFormatted);
|
||||||
messageRightHtml.AppendChild(
|
}
|
||||||
HtmlNode.CreateNode($"<div class=\"msg-content\">{content}</div>"));
|
|
||||||
|
// Attachments
|
||||||
|
foreach (var attachment in message.Attachments)
|
||||||
|
{
|
||||||
|
await writer.WriteLineAsync(attachment.Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await writer.WriteLineAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExportAsHtmlAsync(string filePath, ChannelChatLog log, string css)
|
||||||
|
{
|
||||||
|
using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128 * 1024))
|
||||||
|
{
|
||||||
|
// Generation info
|
||||||
|
await writer.WriteLineAsync("<!-- https://github.com/Tyrrrz/DiscordChatExporter -->");
|
||||||
|
|
||||||
|
// Html start
|
||||||
|
await writer.WriteLineAsync("<!DOCTYPE html>");
|
||||||
|
await writer.WriteLineAsync("<html lang=\"en\">");
|
||||||
|
|
||||||
|
// HEAD
|
||||||
|
await writer.WriteLineAsync("<head>");
|
||||||
|
await writer.WriteLineAsync($"<title>{log.Guild} - {log.Channel}</title>");
|
||||||
|
await writer.WriteLineAsync("<meta charset=\"utf-8\" />");
|
||||||
|
await writer.WriteLineAsync("<meta name=\"viewport\" content=\"width=device-width\" />");
|
||||||
|
await writer.WriteLineAsync($"<style>{css}</style>");
|
||||||
|
await writer.WriteLineAsync("</head>");
|
||||||
|
|
||||||
|
// Body start
|
||||||
|
await writer.WriteLineAsync("<body>");
|
||||||
|
|
||||||
|
// Guild and channel info
|
||||||
|
await writer.WriteLineAsync("<div id=\"info\">");
|
||||||
|
await writer.WriteLineAsync("<div class=\"info-left\">");
|
||||||
|
await writer.WriteLineAsync($"<img class=\"guild-icon\" src=\"{log.Guild.IconUrl}\" />");
|
||||||
|
await writer.WriteLineAsync("</div>"); // info-left
|
||||||
|
await writer.WriteLineAsync("<div class=\"info-right\">");
|
||||||
|
await writer.WriteLineAsync($"<div class=\"guild-name\">{log.Guild}</div>");
|
||||||
|
await writer.WriteLineAsync($"<div class=\"channel-name\">{log.Channel}</div>");
|
||||||
|
await writer.WriteLineAsync($"<div class=\"misc\">{log.TotalMessageCount:N0} messages</div>");
|
||||||
|
await writer.WriteLineAsync("</div>"); // info-right
|
||||||
|
await writer.WriteLineAsync("</div>"); // info
|
||||||
|
|
||||||
|
// Chat log
|
||||||
|
await writer.WriteLineAsync("<div id=\"log\">");
|
||||||
|
foreach (var group in log.MessageGroups)
|
||||||
|
{
|
||||||
|
await writer.WriteLineAsync("<div class=\"msg\">");
|
||||||
|
await writer.WriteLineAsync("<div class=\"msg-left\">");
|
||||||
|
await writer.WriteLineAsync($"<img class=\"msg-avatar\" src=\"{group.Author.AvatarUrl}\" />");
|
||||||
|
await writer.WriteLineAsync("</div>");
|
||||||
|
|
||||||
|
await writer.WriteLineAsync("<div class=\"msg-right\">");
|
||||||
|
await writer.WriteAsync($"<span class=\"msg-user\" title=\"{HtmlEncode(group.Author)}\">");
|
||||||
|
await writer.WriteAsync(HtmlEncode(group.Author.Name));
|
||||||
|
await writer.WriteLineAsync("</span>");
|
||||||
|
var timeStampFormatted = HtmlEncode(group.TimeStamp.ToString(_settingsService.DateFormat));
|
||||||
|
await writer.WriteLineAsync($"<span class=\"msg-date\">{timeStampFormatted}</span>");
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
foreach (var message in group.Messages)
|
||||||
|
{
|
||||||
|
// Content
|
||||||
|
if (message.Content.IsNotBlank())
|
||||||
|
{
|
||||||
|
await writer.WriteLineAsync("<div class=\"msg-content\">");
|
||||||
|
var contentFormatted = FormatMessageContentHtml(message.Content);
|
||||||
|
await writer.WriteAsync(contentFormatted);
|
||||||
|
|
||||||
// Edited timestamp
|
// Edited timestamp
|
||||||
if (message.EditedTimeStamp != null)
|
if (message.EditedTimeStamp != null)
|
||||||
{
|
{
|
||||||
contentHtml.AppendChild(
|
var editedTimeStampFormatted =
|
||||||
HtmlNode.CreateNode(
|
HtmlEncode(message.EditedTimeStamp.Value.ToString(_settingsService.DateFormat));
|
||||||
$"<span class=\"msg-edited\" title=\"{message.EditedTimeStamp.Value.ToString(dateFormat)}\">(edited)</span>"));
|
await writer.WriteAsync(
|
||||||
|
$"<span class=\"msg-edited\" title=\"{editedTimeStampFormatted}\">(edited)</span>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await writer.WriteLineAsync("</div>"); // msg-content
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attachments
|
// Attachments
|
||||||
@@ -100,67 +139,68 @@ namespace DiscordChatExporter.Services
|
|||||||
{
|
{
|
||||||
if (attachment.Type == AttachmentType.Image)
|
if (attachment.Type == AttachmentType.Image)
|
||||||
{
|
{
|
||||||
messageRightHtml.AppendChild(
|
await writer.WriteLineAsync("<div class=\"msg-attachment\">");
|
||||||
HtmlNode.CreateNode("<div class=\"msg-attachment\">" +
|
await writer.WriteLineAsync($"<a href=\"{attachment.Url}\">");
|
||||||
$"<a href=\"{attachment.Url}\">" +
|
await writer.WriteLineAsync(
|
||||||
$"<img class=\"msg-attachment\" src=\"{attachment.Url}\" />" +
|
$"<img class=\"msg-attachment\" src=\"{attachment.Url}\" />");
|
||||||
"</a>" +
|
await writer.WriteLineAsync("</a>");
|
||||||
"</div>"));
|
await writer.WriteLineAsync("</div>");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
messageRightHtml.AppendChild(
|
await writer.WriteLineAsync("<div class=\"msg-attachment\">");
|
||||||
HtmlNode.CreateNode("<div class=\"msg-attachment\">" +
|
await writer.WriteLineAsync($"<a href=\"{attachment.Url}\">");
|
||||||
$"<a href=\"{attachment.Url}\">" +
|
var fileSizeFormatted = FormatFileSize(attachment.FileSize);
|
||||||
$"Attachment: {attachment.FileName} ({NormalizeFileSize(attachment.FileSize)})" +
|
await writer.WriteLineAsync($"Attachment: {attachment.FileName} ({fileSizeFormatted})");
|
||||||
"</a>" +
|
await writer.WriteLineAsync("</a>");
|
||||||
"</div>"));
|
await writer.WriteLineAsync("</div>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await writer.WriteLineAsync("</div>"); // msg-right
|
||||||
|
await writer.WriteLineAsync("</div>"); // msg
|
||||||
}
|
}
|
||||||
|
await writer.WriteLineAsync("</div>"); // log
|
||||||
|
|
||||||
doc.Save(filePath);
|
await writer.WriteLineAsync("</body>");
|
||||||
});
|
await writer.WriteLineAsync("</html>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log)
|
||||||
|
{
|
||||||
|
if (format == ExportFormat.PlainText)
|
||||||
|
{
|
||||||
|
return ExportAsTextAsync(filePath, log);
|
||||||
|
}
|
||||||
|
if (format == ExportFormat.HtmlDark)
|
||||||
|
{
|
||||||
|
var css = Program.GetResourceString("DiscordChatExporter.Resources.ExportService.DarkTheme.css");
|
||||||
|
return ExportAsHtmlAsync(filePath, log, css);
|
||||||
|
}
|
||||||
|
if (format == ExportFormat.HtmlLight)
|
||||||
|
{
|
||||||
|
var css = Program.GetResourceString("DiscordChatExporter.Resources.ExportService.LightTheme.css");
|
||||||
|
return ExportAsHtmlAsync(filePath, log, css);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class ExportService
|
public partial class ExportService
|
||||||
{
|
{
|
||||||
private static HtmlDocument GetTemplate()
|
private static string HtmlEncode(string str)
|
||||||
{
|
{
|
||||||
var resourcePath = "DiscordChatExporter.Resources.ExportService.Template.html";
|
return WebUtility.HtmlEncode(str);
|
||||||
|
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
|
||||||
var stream = assembly.GetManifestResourceStream(resourcePath);
|
|
||||||
if (stream == null)
|
|
||||||
throw new MissingManifestResourceException("Could not find template resource");
|
|
||||||
|
|
||||||
using (stream)
|
|
||||||
{
|
|
||||||
var doc = new HtmlDocument();
|
|
||||||
doc.Load(stream);
|
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetStyle(Theme theme)
|
private static string HtmlEncode(object obj)
|
||||||
{
|
{
|
||||||
var resourcePath = $"DiscordChatExporter.Resources.ExportService.{theme}Theme.css";
|
return WebUtility.HtmlEncode(obj.ToString());
|
||||||
|
|
||||||
var assembly = Assembly.GetExecutingAssembly();
|
|
||||||
var stream = assembly.GetManifestResourceStream(resourcePath);
|
|
||||||
if (stream == null)
|
|
||||||
throw new MissingManifestResourceException("Could not find style resource");
|
|
||||||
|
|
||||||
using (stream)
|
|
||||||
using (var reader = new StreamReader(stream))
|
|
||||||
{
|
|
||||||
return reader.ReadToEnd();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string NormalizeFileSize(long fileSize)
|
private static string FormatFileSize(long fileSize)
|
||||||
{
|
{
|
||||||
string[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
string[] units = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
|
||||||
double size = fileSize;
|
double size = fileSize;
|
||||||
@@ -175,23 +215,23 @@ namespace DiscordChatExporter.Services
|
|||||||
return $"{size:0.#} {units[unit]}";
|
return $"{size:0.#} {units[unit]}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string FormatMessageContent(string content)
|
private static string FormatMessageContentHtml(string content)
|
||||||
{
|
{
|
||||||
// Encode HTML
|
// Encode HTML
|
||||||
content = HtmlDocument.HtmlEncode(content);
|
content = HtmlEncode(content);
|
||||||
|
|
||||||
|
// Preformatted div
|
||||||
|
content = Regex.Replace(content, "```+(?:[^`]*?\\n)?([^`]+)\\n?```+",
|
||||||
|
m => "<div class=\"pre\">" + m.Groups[1].Value + "</div>");
|
||||||
|
|
||||||
|
// Preformatted span
|
||||||
|
content = Regex.Replace(content, "`([^`]+)`",
|
||||||
|
m => "<span class=\"pre\">" + m.Groups[1].Value + "</span>");
|
||||||
|
|
||||||
// Links from URLs
|
// Links from URLs
|
||||||
content = Regex.Replace(content, "((https?|ftp)://[^\\s/$.?#].[^\\s]*)",
|
content = Regex.Replace(content, "((https?|ftp)://[^\\s/$.?#].[^\\s]*)",
|
||||||
"<a href=\"$1\">$1</a>");
|
"<a href=\"$1\">$1</a>");
|
||||||
|
|
||||||
// Preformatted multiline
|
|
||||||
content = Regex.Replace(content, "```([^`]*?)```",
|
|
||||||
m => "<div class=\"pre\">" + m.Groups[1].Value + "</div>");
|
|
||||||
|
|
||||||
// Preformatted inline
|
|
||||||
content = Regex.Replace(content, "`([^`]*?)`",
|
|
||||||
m => "<span class=\"pre\">" + m.Groups[1].Value + "</span>");
|
|
||||||
|
|
||||||
// Bold
|
// Bold
|
||||||
content = Regex.Replace(content, "\\*\\*([^\\*]*?)\\*\\*", "<b>$1</b>");
|
content = Regex.Replace(content, "\\*\\*([^\\*]*?)\\*\\*", "<b>$1</b>");
|
||||||
|
|
||||||
@@ -205,7 +245,7 @@ namespace DiscordChatExporter.Services
|
|||||||
content = Regex.Replace(content, "~~([^~]*?)~~", "<s>$1</s>");
|
content = Regex.Replace(content, "~~([^~]*?)~~", "<s>$1</s>");
|
||||||
|
|
||||||
// New lines
|
// New lines
|
||||||
content = content.Replace("\n", "</br>");
|
content = content.Replace("\n", "<br />");
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Models;
|
using DiscordChatExporter.Models;
|
||||||
|
|
||||||
@@ -6,12 +7,13 @@ namespace DiscordChatExporter.Services
|
|||||||
{
|
{
|
||||||
public interface IDataService
|
public interface IDataService
|
||||||
{
|
{
|
||||||
Task<IEnumerable<Guild>> GetGuildsAsync(string token);
|
Task<IReadOnlyList<Guild>> GetGuildsAsync(string token);
|
||||||
|
|
||||||
Task<IEnumerable<Channel>> GetDirectMessageChannelsAsync(string token);
|
Task<IReadOnlyList<Channel>> GetDirectMessageChannelsAsync(string token);
|
||||||
|
|
||||||
Task<IEnumerable<Channel>> GetGuildChannelsAsync(string token, string guildId);
|
Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(string token, string guildId);
|
||||||
|
|
||||||
Task<IEnumerable<Message>> GetChannelMessagesAsync(string token, string channelId);
|
Task<IReadOnlyList<Message>> GetChannelMessagesAsync(string token, string channelId,
|
||||||
|
DateTime? from, DateTime? to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,6 @@ namespace DiscordChatExporter.Services
|
|||||||
{
|
{
|
||||||
public interface IExportService
|
public interface IExportService
|
||||||
{
|
{
|
||||||
Task ExportAsync(string filePath, ChannelChatLog log, Theme theme);
|
Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,6 @@ namespace DiscordChatExporter.Services
|
|||||||
{
|
{
|
||||||
public interface IMessageGroupService
|
public interface IMessageGroupService
|
||||||
{
|
{
|
||||||
IEnumerable<MessageGroup> GroupMessages(IEnumerable<Message> messages);
|
IReadOnlyList<MessageGroup> GroupMessages(IReadOnlyList<Message> messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,12 @@ namespace DiscordChatExporter.Services
|
|||||||
{
|
{
|
||||||
public interface ISettingsService
|
public interface ISettingsService
|
||||||
{
|
{
|
||||||
string Token { get; set; }
|
|
||||||
Theme Theme { get; set; }
|
|
||||||
string DateFormat { get; set; }
|
string DateFormat { get; set; }
|
||||||
int MessageGroupLimit { get; set; }
|
int MessageGroupLimit { get; set; }
|
||||||
|
|
||||||
|
string LastToken { get; set; }
|
||||||
|
ExportFormat LastExportFormat { get; set; }
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
void Save();
|
void Save();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ namespace DiscordChatExporter.Services
|
|||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<MessageGroup> GroupMessages(IEnumerable<Message> messages)
|
public IReadOnlyList<MessageGroup> GroupMessages(IReadOnlyList<Message> messages)
|
||||||
{
|
{
|
||||||
var groupLimit = _settingsService.MessageGroupLimit;
|
|
||||||
var result = new List<MessageGroup>();
|
var result = new List<MessageGroup>();
|
||||||
|
|
||||||
// Group adjacent messages by timestamp and author
|
// Group adjacent messages by timestamp and author
|
||||||
@@ -31,13 +30,13 @@ namespace DiscordChatExporter.Services
|
|||||||
message.Author.Id != groupFirst.Author.Id ||
|
message.Author.Id != groupFirst.Author.Id ||
|
||||||
(message.TimeStamp - groupFirst.TimeStamp).TotalHours > 1 ||
|
(message.TimeStamp - groupFirst.TimeStamp).TotalHours > 1 ||
|
||||||
message.TimeStamp.Hour != groupFirst.TimeStamp.Hour ||
|
message.TimeStamp.Hour != groupFirst.TimeStamp.Hour ||
|
||||||
groupBuffer.Count >= groupLimit
|
groupBuffer.Count >= _settingsService.MessageGroupLimit
|
||||||
);
|
);
|
||||||
|
|
||||||
// If condition is true - flush buffer
|
// If condition is true - flush buffer
|
||||||
if (breakCondition)
|
if (breakCondition)
|
||||||
{
|
{
|
||||||
var group = new MessageGroup(groupFirst.Author, groupFirst.TimeStamp, groupBuffer);
|
var group = new MessageGroup(groupFirst.Author, groupFirst.TimeStamp, groupBuffer.ToArray());
|
||||||
result.Add(group);
|
result.Add(group);
|
||||||
groupBuffer.Clear();
|
groupBuffer.Clear();
|
||||||
}
|
}
|
||||||
@@ -50,7 +49,7 @@ namespace DiscordChatExporter.Services
|
|||||||
if (groupBuffer.Any())
|
if (groupBuffer.Any())
|
||||||
{
|
{
|
||||||
var groupFirst = groupBuffer.First();
|
var groupFirst = groupBuffer.First();
|
||||||
var group = new MessageGroup(groupFirst.Author, groupFirst.TimeStamp, groupBuffer);
|
var group = new MessageGroup(groupFirst.Author, groupFirst.TimeStamp, groupBuffer.ToArray());
|
||||||
result.Add(group);
|
result.Add(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ namespace DiscordChatExporter.Services
|
|||||||
{
|
{
|
||||||
public class SettingsService : SettingsManager, ISettingsService
|
public class SettingsService : SettingsManager, ISettingsService
|
||||||
{
|
{
|
||||||
public string Token { get; set; }
|
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
|
||||||
public Theme Theme { get; set; }
|
|
||||||
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm";
|
|
||||||
public int MessageGroupLimit { get; set; } = 20;
|
public int MessageGroupLimit { get; set; } = 20;
|
||||||
|
|
||||||
|
public string LastToken { get; set; }
|
||||||
|
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
|
||||||
public SettingsService()
|
public SettingsService()
|
||||||
{
|
{
|
||||||
Configuration.StorageSpace = StorageSpace.Instance;
|
Configuration.StorageSpace = StorageSpace.Instance;
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
|
|
||||||
public ErrorViewModel()
|
public ErrorViewModel()
|
||||||
{
|
{
|
||||||
MessengerInstance.Register<ShowErrorMessage>(this, m => Message = m.Message);
|
// Messages
|
||||||
|
MessengerInstance.Register<ShowErrorMessage>(this, m =>
|
||||||
|
{
|
||||||
|
Message = m.Message;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,10 +14,14 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
|
|
||||||
public ExportDoneViewModel()
|
public ExportDoneViewModel()
|
||||||
{
|
{
|
||||||
MessengerInstance.Register<ShowExportDoneMessage>(this, m => _filePath = m.FilePath);
|
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
OpenCommand = new RelayCommand(Open);
|
OpenCommand = new RelayCommand(Open);
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
MessengerInstance.Register<ShowExportDoneMessage>(this, m =>
|
||||||
|
{
|
||||||
|
_filePath = m.FilePath;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Open()
|
private void Open()
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using DiscordChatExporter.Messages;
|
||||||
|
using DiscordChatExporter.Models;
|
||||||
|
using DiscordChatExporter.Services;
|
||||||
|
using GalaSoft.MvvmLight;
|
||||||
|
using GalaSoft.MvvmLight.CommandWpf;
|
||||||
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.ViewModels
|
||||||
|
{
|
||||||
|
public class ExportSetupViewModel : ViewModelBase, IExportSetupViewModel
|
||||||
|
{
|
||||||
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
|
private string _filePath;
|
||||||
|
private ExportFormat _format;
|
||||||
|
private DateTime? _from;
|
||||||
|
private DateTime? _to;
|
||||||
|
|
||||||
|
public Guild Guild { get; private set; }
|
||||||
|
|
||||||
|
public Channel Channel { get; private set; }
|
||||||
|
|
||||||
|
public string FilePath
|
||||||
|
{
|
||||||
|
get => _filePath;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Set(ref _filePath, value);
|
||||||
|
ExportCommand.RaiseCanExecuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<ExportFormat> AvailableFormats { get; }
|
||||||
|
|
||||||
|
public ExportFormat SelectedFormat
|
||||||
|
{
|
||||||
|
get => _format;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Set(ref _format, value);
|
||||||
|
|
||||||
|
// Replace extension in path
|
||||||
|
var newExt = value.GetFileExtension();
|
||||||
|
if (FilePath != null && !FilePath.EndsWith(newExt))
|
||||||
|
FilePath = FilePath.SubstringUntilLast(".") + "." + newExt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? From
|
||||||
|
{
|
||||||
|
get => _from;
|
||||||
|
set => Set(ref _from, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime? To
|
||||||
|
{
|
||||||
|
get => _to;
|
||||||
|
set => Set(ref _to, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
public RelayCommand ExportCommand { get; }
|
||||||
|
|
||||||
|
public ExportSetupViewModel(ISettingsService settingsService)
|
||||||
|
{
|
||||||
|
_settingsService = settingsService;
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
AvailableFormats = Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
ExportCommand = new RelayCommand(Export, () => FilePath.IsNotBlank());
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
MessengerInstance.Register<ShowExportSetupMessage>(this, m =>
|
||||||
|
{
|
||||||
|
Guild = m.Guild;
|
||||||
|
Channel = m.Channel;
|
||||||
|
SelectedFormat = _settingsService.LastExportFormat;
|
||||||
|
FilePath = $"{Guild} - {Channel}.{SelectedFormat.GetFileExtension()}"
|
||||||
|
.Replace(Path.GetInvalidFileNameChars(), '_');
|
||||||
|
From = null;
|
||||||
|
To = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Export()
|
||||||
|
{
|
||||||
|
// Save format
|
||||||
|
_settingsService.LastExportFormat = SelectedFormat;
|
||||||
|
|
||||||
|
// Start export
|
||||||
|
MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat, From, To));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using DiscordChatExporter.Models;
|
||||||
|
using GalaSoft.MvvmLight.CommandWpf;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.ViewModels
|
||||||
|
{
|
||||||
|
public interface IExportSetupViewModel
|
||||||
|
{
|
||||||
|
Guild Guild { get; }
|
||||||
|
Channel Channel { get; }
|
||||||
|
string FilePath { get; set; }
|
||||||
|
IReadOnlyList<ExportFormat> AvailableFormats { get; }
|
||||||
|
ExportFormat SelectedFormat { get; set; }
|
||||||
|
DateTime? From { get; set; }
|
||||||
|
DateTime? To { get; set; }
|
||||||
|
|
||||||
|
RelayCommand ExportCommand { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,8 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
IReadOnlyList<Channel> AvailableChannels { get; }
|
IReadOnlyList<Channel> AvailableChannels { get; }
|
||||||
|
|
||||||
RelayCommand PullDataCommand { get; }
|
RelayCommand PullDataCommand { get; }
|
||||||
RelayCommand<Channel> ExportChannelCommand { get; }
|
|
||||||
RelayCommand ShowSettingsCommand { get; }
|
RelayCommand ShowSettingsCommand { get; }
|
||||||
RelayCommand ShowAboutCommand { get; }
|
RelayCommand ShowAboutCommand { get; }
|
||||||
|
RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
namespace DiscordChatExporter.ViewModels
|
||||||
using DiscordChatExporter.Models;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.ViewModels
|
|
||||||
{
|
{
|
||||||
public interface ISettingsViewModel
|
public interface ISettingsViewModel
|
||||||
{
|
{
|
||||||
IReadOnlyList<Theme> AvailableThemes { get; }
|
|
||||||
Theme Theme { get; set; }
|
|
||||||
string DateFormat { get; set; }
|
string DateFormat { get; set; }
|
||||||
int MessageGroupLimit { get; set; }
|
int MessageGroupLimit { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
using DiscordChatExporter.Exceptions;
|
using DiscordChatExporter.Exceptions;
|
||||||
using DiscordChatExporter.Messages;
|
using DiscordChatExporter.Messages;
|
||||||
using DiscordChatExporter.Models;
|
using DiscordChatExporter.Models;
|
||||||
using DiscordChatExporter.Services;
|
using DiscordChatExporter.Services;
|
||||||
using GalaSoft.MvvmLight;
|
using GalaSoft.MvvmLight;
|
||||||
using GalaSoft.MvvmLight.CommandWpf;
|
using GalaSoft.MvvmLight.CommandWpf;
|
||||||
using Microsoft.Win32;
|
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.ViewModels
|
namespace DiscordChatExporter.ViewModels
|
||||||
@@ -23,10 +23,10 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
|
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
|
||||||
|
|
||||||
private bool _isBusy;
|
private bool _isBusy;
|
||||||
|
private string _token;
|
||||||
private IReadOnlyList<Guild> _availableGuilds;
|
private IReadOnlyList<Guild> _availableGuilds;
|
||||||
private Guild _selectedGuild;
|
private Guild _selectedGuild;
|
||||||
private IReadOnlyList<Channel> _availableChannels;
|
private IReadOnlyList<Channel> _availableChannels;
|
||||||
private string _cachedToken;
|
|
||||||
|
|
||||||
public bool IsBusy
|
public bool IsBusy
|
||||||
{
|
{
|
||||||
@@ -35,7 +35,7 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
{
|
{
|
||||||
Set(ref _isBusy, value);
|
Set(ref _isBusy, value);
|
||||||
PullDataCommand.RaiseCanExecuteChanged();
|
PullDataCommand.RaiseCanExecuteChanged();
|
||||||
ExportChannelCommand.RaiseCanExecuteChanged();
|
ShowExportSetupCommand.RaiseCanExecuteChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,13 +43,13 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
|
|
||||||
public string Token
|
public string Token
|
||||||
{
|
{
|
||||||
get => _settingsService.Token;
|
get => _token;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
// Remove invalid chars
|
// Remove invalid chars
|
||||||
value = value?.Trim('"');
|
value = value?.Trim('"');
|
||||||
|
|
||||||
_settingsService.Token = value;
|
Set(ref _token, value);
|
||||||
PullDataCommand.RaiseCanExecuteChanged();
|
PullDataCommand.RaiseCanExecuteChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
{
|
{
|
||||||
Set(ref _selectedGuild, value);
|
Set(ref _selectedGuild, value);
|
||||||
AvailableChannels = value != null ? _guildChannelsMap[value] : new Channel[0];
|
AvailableChannels = value != null ? _guildChannelsMap[value] : new Channel[0];
|
||||||
ExportChannelCommand.RaiseCanExecuteChanged();
|
ShowExportSetupCommand.RaiseCanExecuteChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +82,9 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
}
|
}
|
||||||
|
|
||||||
public RelayCommand PullDataCommand { get; }
|
public RelayCommand PullDataCommand { get; }
|
||||||
public RelayCommand<Channel> ExportChannelCommand { get; }
|
|
||||||
public RelayCommand ShowSettingsCommand { get; }
|
public RelayCommand ShowSettingsCommand { get; }
|
||||||
public RelayCommand ShowAboutCommand { get; }
|
public RelayCommand ShowAboutCommand { get; }
|
||||||
|
public RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||||
|
|
||||||
public MainViewModel(ISettingsService settingsService, IDataService dataService,
|
public MainViewModel(ISettingsService settingsService, IDataService dataService,
|
||||||
IMessageGroupService messageGroupService, IExportService exportService)
|
IMessageGroupService messageGroupService, IExportService exportService)
|
||||||
@@ -98,15 +98,29 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
|
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
|
||||||
ExportChannelCommand = new RelayCommand<Channel>(ExportChannel, _ => !IsBusy);
|
|
||||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||||
|
ShowExportSetupCommand = new RelayCommand<Channel>(ShowExportSetup, _ => !IsBusy);
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
MessengerInstance.Register<StartExportMessage>(this, m =>
|
||||||
|
{
|
||||||
|
Export(m.Channel, m.FilePath, m.Format, m.From, m.To);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
_token = _settingsService.LastToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void PullData()
|
private async void PullData()
|
||||||
{
|
{
|
||||||
IsBusy = true;
|
IsBusy = true;
|
||||||
_cachedToken = Token;
|
|
||||||
|
// Copy token so it doesn't get mutated
|
||||||
|
var token = Token;
|
||||||
|
|
||||||
|
// Save token
|
||||||
|
_settingsService.LastToken = token;
|
||||||
|
|
||||||
// Clear existing
|
// Clear existing
|
||||||
_guildChannelsMap.Clear();
|
_guildChannelsMap.Clear();
|
||||||
@@ -115,25 +129,30 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
{
|
{
|
||||||
// Get DM channels
|
// Get DM channels
|
||||||
{
|
{
|
||||||
var channels = await _dataService.GetDirectMessageChannelsAsync(_cachedToken);
|
var channels = await _dataService.GetDirectMessageChannelsAsync(token);
|
||||||
var guild = new Guild("@me", "Direct Messages", null);
|
var guild = new Guild("@me", "Direct Messages", null);
|
||||||
_guildChannelsMap[guild] = channels.ToArray();
|
_guildChannelsMap[guild] = channels.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get guild channels
|
// Get guild channels
|
||||||
{
|
{
|
||||||
var guilds = await _dataService.GetGuildsAsync(_cachedToken);
|
var guilds = await _dataService.GetGuildsAsync(token);
|
||||||
foreach (var guild in guilds)
|
foreach (var guild in guilds)
|
||||||
{
|
{
|
||||||
var channels = await _dataService.GetGuildChannelsAsync(_cachedToken, guild.Id);
|
var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
||||||
channels = channels.Where(c => c.Type == ChannelType.GuildTextChat);
|
_guildChannelsMap[guild] = channels.Where(c => c.Type == ChannelType.GuildTextChat).ToArray();
|
||||||
_guildChannelsMap[guild] = channels.ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (UnathorizedException)
|
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
{
|
{
|
||||||
MessengerInstance.Send(new ShowErrorMessage("Failed to authorize. Make sure the token is valid."));
|
const string message = "Unauthorized to perform request. Make sure token is valid.";
|
||||||
|
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||||
|
}
|
||||||
|
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
const string message = "Forbidden to perform request. The account may be locked by 2FA.";
|
||||||
|
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
AvailableGuilds = _guildChannelsMap.Keys.ToArray();
|
AvailableGuilds = _guildChannelsMap.Keys.ToArray();
|
||||||
@@ -141,54 +160,6 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
IsBusy = false;
|
IsBusy = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ExportChannel(Channel channel)
|
|
||||||
{
|
|
||||||
IsBusy = true;
|
|
||||||
|
|
||||||
// Get safe file names
|
|
||||||
var safeGuildName = SelectedGuild.Name.Replace(Path.GetInvalidFileNameChars(), '_');
|
|
||||||
var safeChannelName = channel.Name.Replace(Path.GetInvalidFileNameChars(), '_');
|
|
||||||
|
|
||||||
// Ask for path
|
|
||||||
var sfd = new SaveFileDialog
|
|
||||||
{
|
|
||||||
FileName = $"{safeGuildName} - {safeChannelName}.html",
|
|
||||||
Filter = "HTML files (*.html)|*.html|All files (*.*)|*.*",
|
|
||||||
DefaultExt = "html",
|
|
||||||
AddExtension = true
|
|
||||||
};
|
|
||||||
if (sfd.ShowDialog() != true)
|
|
||||||
{
|
|
||||||
IsBusy = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Get messages
|
|
||||||
var messages = await _dataService.GetChannelMessagesAsync(_cachedToken, channel.Id);
|
|
||||||
|
|
||||||
// Group them
|
|
||||||
var messageGroups = _messageGroupService.GroupMessages(messages);
|
|
||||||
|
|
||||||
// Create log
|
|
||||||
var chatLog = new ChannelChatLog(SelectedGuild, channel, messageGroups);
|
|
||||||
|
|
||||||
// Export
|
|
||||||
await _exportService.ExportAsync(sfd.FileName, chatLog, _settingsService.Theme);
|
|
||||||
|
|
||||||
// Show dialog
|
|
||||||
MessengerInstance.Send(new ShowExportDoneMessage(sfd.FileName));
|
|
||||||
}
|
|
||||||
catch (UnathorizedException)
|
|
||||||
{
|
|
||||||
MessengerInstance.Send(new ShowErrorMessage("Failed to export. You don't have access to that channel."));
|
|
||||||
}
|
|
||||||
|
|
||||||
IsBusy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowSettings()
|
private void ShowSettings()
|
||||||
{
|
{
|
||||||
MessengerInstance.Send(new ShowSettingsMessage());
|
MessengerInstance.Send(new ShowSettingsMessage());
|
||||||
@@ -198,5 +169,43 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
{
|
{
|
||||||
Process.Start("https://github.com/Tyrrrz/DiscordChatExporter");
|
Process.Start("https://github.com/Tyrrrz/DiscordChatExporter");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowExportSetup(Channel channel)
|
||||||
|
{
|
||||||
|
MessengerInstance.Send(new ShowExportSetupMessage(SelectedGuild, channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Export(Channel channel, string filePath, ExportFormat format, DateTime? from, DateTime? to)
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
|
||||||
|
// Get last used token
|
||||||
|
var token = _settingsService.LastToken;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Get messages
|
||||||
|
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to);
|
||||||
|
|
||||||
|
// Group them
|
||||||
|
var messageGroups = _messageGroupService.GroupMessages(messages);
|
||||||
|
|
||||||
|
// Create log
|
||||||
|
var log = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count);
|
||||||
|
|
||||||
|
// Export
|
||||||
|
await _exportService.ExportAsync(format, filePath, log);
|
||||||
|
|
||||||
|
// Notify completion
|
||||||
|
MessengerInstance.Send(new ShowExportDoneMessage(filePath));
|
||||||
|
}
|
||||||
|
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
const string message = "Forbidden to view messages in that channel.";
|
||||||
|
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using DiscordChatExporter.Services;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using DiscordChatExporter.Models;
|
|
||||||
using DiscordChatExporter.Services;
|
|
||||||
using GalaSoft.MvvmLight;
|
using GalaSoft.MvvmLight;
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
@@ -12,14 +8,6 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
{
|
{
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
|
|
||||||
public IReadOnlyList<Theme> AvailableThemes { get; }
|
|
||||||
|
|
||||||
public Theme Theme
|
|
||||||
{
|
|
||||||
get => _settingsService.Theme;
|
|
||||||
set => _settingsService.Theme = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string DateFormat
|
public string DateFormat
|
||||||
{
|
{
|
||||||
get => _settingsService.DateFormat;
|
get => _settingsService.DateFormat;
|
||||||
@@ -35,9 +23,6 @@ namespace DiscordChatExporter.ViewModels
|
|||||||
public SettingsViewModel(ISettingsService settingsService)
|
public SettingsViewModel(ISettingsService settingsService)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
|
|
||||||
// Defaults
|
|
||||||
AvailableThemes = Enum.GetValues(typeof(Theme)).Cast<Theme>().ToArray();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
using System;
|
namespace DiscordChatExporter.Views
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Views
|
|
||||||
{
|
{
|
||||||
public partial class ErrorDialog
|
public partial class ErrorDialog
|
||||||
{
|
{
|
||||||
@@ -13,4 +7,4 @@ namespace DiscordChatExporter.Views
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,8 @@ UserControl "DiscordChatExporter.Views.ExportDoneDialog" {
|
|||||||
HorizontalAlignment: Right
|
HorizontalAlignment: Right
|
||||||
|
|
||||||
// Open
|
// Open
|
||||||
Button {
|
Button "OpenButton" {
|
||||||
|
Click: OpenButton_Click
|
||||||
Command: bind OpenCommand
|
Command: bind OpenCommand
|
||||||
Content: "OPEN IT"
|
Content: "OPEN IT"
|
||||||
Margin: 8
|
Margin: 8
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System;
|
using System.Windows;
|
||||||
using System.Collections.Generic;
|
using MaterialDesignThemes.Wpf;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Views
|
namespace DiscordChatExporter.Views
|
||||||
{
|
{
|
||||||
@@ -12,5 +9,10 @@ namespace DiscordChatExporter.Views
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OpenButton_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
DialogHost.CloseDialogCommand.Execute(null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
using DiscordChatExporter.Models
|
||||||
|
using MaterialDesignThemes.Wpf
|
||||||
|
|
||||||
|
UserControl "DiscordChatExporter.Views.ExportSetupDialog" {
|
||||||
|
DataContext: bind ExportSetupViewModel from $resource Container
|
||||||
|
Width: 325
|
||||||
|
|
||||||
|
StackPanel {
|
||||||
|
// File path
|
||||||
|
TextBox {
|
||||||
|
Margin: "16 16 16 8"
|
||||||
|
HintAssist.Hint: "Output file"
|
||||||
|
HintAssist.IsFloating: true
|
||||||
|
IsReadOnly: true
|
||||||
|
Text: bind FilePath
|
||||||
|
set [ UpdateSourceTrigger: PropertyChanged ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format
|
||||||
|
ComboBox {
|
||||||
|
Margin: "16 8 16 8"
|
||||||
|
HintAssist.Hint: "Export format"
|
||||||
|
HintAssist.IsFloating: true
|
||||||
|
IsReadOnly: true
|
||||||
|
ItemsSource: bind AvailableFormats
|
||||||
|
ItemTemplate: DataTemplate {
|
||||||
|
TextBlock {
|
||||||
|
Text: bind
|
||||||
|
convert (ExportFormat f) => Extensions.GetDisplayName(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectedItem: bind SelectedFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
// Date range
|
||||||
|
Grid {
|
||||||
|
#TwoColumns("*", "*")
|
||||||
|
|
||||||
|
DatePicker {
|
||||||
|
Grid.Column: 0
|
||||||
|
Margin: "16 20 8 8"
|
||||||
|
HintAssist.Hint: "From (optional)"
|
||||||
|
HintAssist.IsFloating: true
|
||||||
|
SelectedDate: bind From
|
||||||
|
}
|
||||||
|
|
||||||
|
DatePicker {
|
||||||
|
Grid.Column: 1
|
||||||
|
Margin: "8 20 16 8"
|
||||||
|
HintAssist.Hint: "To (optional)"
|
||||||
|
HintAssist.IsFloating: true
|
||||||
|
SelectedDate: bind To
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
@StackPanelHorizontal {
|
||||||
|
HorizontalAlignment: Right
|
||||||
|
|
||||||
|
// Browse
|
||||||
|
Button "BrowseButton" {
|
||||||
|
Click: BrowseButton_Click
|
||||||
|
Content: "BROWSE"
|
||||||
|
Margin: 8
|
||||||
|
Style: resource dyn "MaterialDesignFlatButton"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export
|
||||||
|
Button "ExportButton" {
|
||||||
|
Click: ExportButton_Click
|
||||||
|
Command: bind ExportCommand
|
||||||
|
Content: "EXPORT"
|
||||||
|
Margin: 8
|
||||||
|
Style: resource dyn "MaterialDesignFlatButton"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel
|
||||||
|
Button {
|
||||||
|
Command: DialogHost.CloseDialogCommand
|
||||||
|
Content: "CANCEL"
|
||||||
|
Margin: 8
|
||||||
|
Style: resource dyn "MaterialDesignFlatButton"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using DiscordChatExporter.Models;
|
||||||
|
using DiscordChatExporter.ViewModels;
|
||||||
|
using MaterialDesignThemes.Wpf;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Views
|
||||||
|
{
|
||||||
|
public partial class ExportSetupDialog
|
||||||
|
{
|
||||||
|
private IExportSetupViewModel ViewModel => (IExportSetupViewModel) DataContext;
|
||||||
|
|
||||||
|
public ExportSetupDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BrowseButton_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
// Get file extension of the selected format
|
||||||
|
var ext = ViewModel.SelectedFormat.GetFileExtension();
|
||||||
|
|
||||||
|
// Open dialog
|
||||||
|
var sfd = new SaveFileDialog
|
||||||
|
{
|
||||||
|
FileName = ViewModel.FilePath,
|
||||||
|
Filter = $"{ext.ToUpperInvariant()} Files|*.{ext}|All Files|*.*",
|
||||||
|
AddExtension = true,
|
||||||
|
Title = "Select output file"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign new file path if dialog was successful
|
||||||
|
if (sfd.ShowDialog() == true)
|
||||||
|
{
|
||||||
|
ViewModel.FilePath = sfd.FileName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExportButton_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
DialogHost.CloseDialogCommand.Execute(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,6 +20,9 @@ Window "DiscordChatExporter.Views.MainWindow" {
|
|||||||
|
|
||||||
DialogHost {
|
DialogHost {
|
||||||
DockPanel {
|
DockPanel {
|
||||||
|
IsEnabled: bind IsBusy
|
||||||
|
convert (bool b) => b ? false : true
|
||||||
|
|
||||||
// Toolbar
|
// Toolbar
|
||||||
Border {
|
Border {
|
||||||
DockPanel.Dock: Top
|
DockPanel.Dock: Top
|
||||||
@@ -46,6 +49,8 @@ Window "DiscordChatExporter.Views.MainWindow" {
|
|||||||
FontSize: 16
|
FontSize: 16
|
||||||
Text: bind Token
|
Text: bind Token
|
||||||
set [ UpdateSourceTrigger: PropertyChanged ]
|
set [ UpdateSourceTrigger: PropertyChanged ]
|
||||||
|
TextFieldAssist.DecorationVisibility: Hidden
|
||||||
|
TextFieldAssist.TextBoxViewMargin: "0 0 2 0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit
|
// Submit
|
||||||
@@ -98,8 +103,6 @@ Window "DiscordChatExporter.Views.MainWindow" {
|
|||||||
Grid {
|
Grid {
|
||||||
DockPanel {
|
DockPanel {
|
||||||
Background: resource dyn "MaterialDesignCardBackground"
|
Background: resource dyn "MaterialDesignCardBackground"
|
||||||
IsEnabled: bind IsBusy
|
|
||||||
convert (bool b) => b ? false : true
|
|
||||||
Visibility: bind IsDataAvailable
|
Visibility: bind IsDataAvailable
|
||||||
convert (bool b) => b ? Visibility.Visible : Visibility.Hidden
|
convert (bool b) => b ? Visibility.Visible : Visibility.Hidden
|
||||||
|
|
||||||
@@ -167,7 +170,7 @@ Window "DiscordChatExporter.Views.MainWindow" {
|
|||||||
Cursor: CursorType.Hand
|
Cursor: CursorType.Hand
|
||||||
InputBindings: [
|
InputBindings: [
|
||||||
MouseBinding {
|
MouseBinding {
|
||||||
Command: bind DataContext.ExportChannelCommand from $ancestor<ItemsControl>
|
Command: bind DataContext.ShowExportSetupCommand from $ancestor<ItemsControl>
|
||||||
CommandParameter: bind
|
CommandParameter: bind
|
||||||
MouseAction: LeftClick
|
MouseAction: LeftClick
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ namespace DiscordChatExporter.Views
|
|||||||
Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}";
|
Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}";
|
||||||
|
|
||||||
Messenger.Default.Register<ShowErrorMessage>(this, m => DialogHost.Show(new ErrorDialog()).Forget());
|
Messenger.Default.Register<ShowErrorMessage>(this, m => DialogHost.Show(new ErrorDialog()).Forget());
|
||||||
Messenger.Default.Register<ShowSettingsMessage>(this, m => DialogHost.Show(new SettingsDialog()).Forget());
|
|
||||||
Messenger.Default.Register<ShowExportDoneMessage>(this, m => DialogHost.Show(new ExportDoneDialog()).Forget());
|
Messenger.Default.Register<ShowExportDoneMessage>(this, m => DialogHost.Show(new ExportDoneDialog()).Forget());
|
||||||
|
Messenger.Default.Register<ShowExportSetupMessage>(this, m => DialogHost.Show(new ExportSetupDialog()).Forget());
|
||||||
|
Messenger.Default.Register<ShowSettingsMessage>(this, m => DialogHost.Show(new SettingsDialog()).Forget());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TokenTextBox_KeyDown(object sender, KeyEventArgs e)
|
public void TokenTextBox_KeyDown(object sender, KeyEventArgs e)
|
||||||
|
|||||||
@@ -5,19 +5,9 @@ UserControl "DiscordChatExporter.Views.SettingsDialog" {
|
|||||||
Width: 250
|
Width: 250
|
||||||
|
|
||||||
StackPanel {
|
StackPanel {
|
||||||
// Theme
|
|
||||||
ComboBox {
|
|
||||||
Margin: "16 16 16 8"
|
|
||||||
HintAssist.Hint: "Theme"
|
|
||||||
HintAssist.IsFloating: true
|
|
||||||
IsReadOnly: true
|
|
||||||
ItemsSource: bind AvailableThemes
|
|
||||||
SelectedItem: bind Theme
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date format
|
// Date format
|
||||||
TextBox {
|
TextBox {
|
||||||
Margin: "16 8 16 8"
|
Margin: "16 16 16 8"
|
||||||
HintAssist.Hint: "Date format"
|
HintAssist.Hint: "Date format"
|
||||||
HintAssist.IsFloating: true
|
HintAssist.IsFloating: true
|
||||||
Text: bind DateFormat
|
Text: bind DateFormat
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
<package id="Ammy" version="1.2.87" targetFramework="net461" />
|
<package id="Ammy" version="1.2.87" targetFramework="net461" />
|
||||||
<package id="Ammy.WPF" version="1.2.87" targetFramework="net461" />
|
<package id="Ammy.WPF" version="1.2.87" targetFramework="net461" />
|
||||||
<package id="CommonServiceLocator" version="1.3" targetFramework="net461" />
|
<package id="CommonServiceLocator" version="1.3" targetFramework="net461" />
|
||||||
<package id="HtmlAgilityPack" version="1.5.5" targetFramework="net461" />
|
|
||||||
<package id="MaterialDesignColors" version="1.1.3" targetFramework="net461" />
|
<package id="MaterialDesignColors" version="1.1.3" targetFramework="net461" />
|
||||||
<package id="MaterialDesignThemes" version="2.3.1.953" targetFramework="net461" />
|
<package id="MaterialDesignThemes" version="2.3.1.953" targetFramework="net461" />
|
||||||
<package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net461" />
|
<package id="MvvmLightLibs" version="5.3.0.0" targetFramework="net461" />
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# DiscordChatExporter
|
# DiscordChatExporter
|
||||||
|
|
||||||
DiscordChatExporter can be used to export message history from [Discord](https://discordapp.com) to an HTML file. It works for both direct message chats and guild chats, supports markdown, message grouping, and attachments. There are options to configure the output, such as date format, color theme, message grouping limit, etc.
|
DiscordChatExporter can be used to export message history from a [Discord](https://discordapp.com) channel to a file. It works for both direct message chats and guild chats, supports markdown, message grouping, and attachments. The tool also lets you select from/to dates to limit the exported messages. There are options to configure the output, such as date format, color theme, message grouping limit, etc.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
@@ -12,15 +13,17 @@ DiscordChatExporter can be used to export message history from [Discord](https:/
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Exports to a self-contained HTML file
|
- Intuitive GUI that displays available guilds and channels
|
||||||
- Supports both dark and light theme
|
- Date ranges to limit messages
|
||||||
- Displays user avatars
|
|
||||||
- Groups messages by author and time
|
- Groups messages by author and time
|
||||||
- Handles Discord markdown characters
|
- Export to a plain text file
|
||||||
- Converts URLs to links
|
- Export to an HTML file
|
||||||
- Inlines attached images with a link to the full version
|
- Dark and light themes
|
||||||
- Inserts download links for other types of attached files
|
- User avatars
|
||||||
- Marks edited messages with a timestamp
|
- Inline image attachments
|
||||||
|
- Full markdown support
|
||||||
|
- Automatic links
|
||||||
|
- Styled similarly to the app
|
||||||
|
|
||||||
## Libraries used
|
## Libraries used
|
||||||
|
|
||||||
@@ -28,6 +31,5 @@ DiscordChatExporter can be used to export message history from [Discord](https:/
|
|||||||
- [GalaSoft.MVVMLight](http://www.mvvmlight.net)
|
- [GalaSoft.MVVMLight](http://www.mvvmlight.net)
|
||||||
- [MaterialDesignInXamlToolkit](https://github.com/ButchersBoy/MaterialDesignInXamlToolkit)
|
- [MaterialDesignInXamlToolkit](https://github.com/ButchersBoy/MaterialDesignInXamlToolkit)
|
||||||
- [Newtonsoft.Json](http://www.newtonsoft.com/json)
|
- [Newtonsoft.Json](http://www.newtonsoft.com/json)
|
||||||
- [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack)
|
|
||||||
- [Tyrrrz.Extensions](https://github.com/Tyrrrz/Extensions)
|
- [Tyrrrz.Extensions](https://github.com/Tyrrrz/Extensions)
|
||||||
- [Tyrrrz.Settings](https://github.com/Tyrrrz/Settings)
|
- [Tyrrrz.Settings](https://github.com/Tyrrrz/Settings)
|
||||||
|
|||||||
Reference in New Issue
Block a user