Compare commits

...

29 Commits

Author SHA1 Message Date
Alexey Golub 71e6a8de7a Update version 2019-06-15 18:16:17 +03:00
Alexey Golub 592b8b6294 Update nuget packages 2019-06-15 18:09:56 +03:00
Alexey Golub e6edcd27a4 [CSV] Render reactions
Addresses the other part of #168
2019-06-11 22:45:19 +03:00
Alexey Golub 557b5be844 [TXT] Render reactions
Addresses part of #168
2019-06-11 22:39:17 +03:00
Alexey Golub 02fe2a46e8 [TXT] Add support for embeds and change how attachments are rendered
Closes #165
2019-06-10 22:16:37 +03:00
Alexey Golub 76c7f9b419 Update version 2019-06-06 23:38:06 +03:00
Alexey Golub 9a383d2bd4 Convert from DateTime to DateTimeOffset instead of parsing it directly
Json.NET is really not meant to be used with DateTimeOffset it seems

Fixes #179
2019-06-06 23:31:40 +03:00
Oleksii Holub 2ed374ee5c [HTML] Render user ID inside data-user-id attribute
Closes #93
2019-06-06 17:43:30 +03:00
Alexey Golub 0ced31ca4a Update version 2019-05-15 01:04:21 +03:00
Alexey Golub 3bb5638b56 Improve usage instructions slightly 2019-05-15 00:56:50 +03:00
Malcolm Diller 3b5d752c95 Add bot tags and some CSS fixes (#172) 2019-05-09 13:52:46 +03:00
Alexey Golub 2b1f599cbe Add logo image 2019-04-25 22:14:17 +03:00
Alexey Golub 7cb1c21897 Refactor 2019-04-18 22:21:20 +03:00
Alexey Golub 37bcea86e3 [CLI] Update usage guide to explain steps needed to extract DM channel ID 2019-04-18 22:19:26 +03:00
Alexey Golub 559ef71d2f Get rid of Chocolatey 2019-04-17 00:59:22 +03:00
Alexey Golub ab0ce478db Move UpdateService to GUI project 2019-04-16 23:08:38 +03:00
Alexey Golub afb9f52221 Update FodyWeavers.xsd 2019-04-16 23:08:22 +03:00
Alexey Golub d65ca409b1 Update nuget packages 2019-04-13 02:42:03 +03:00
Oleksii Holub 7784383ea7 Fix grammar mistakes in comments 2019-04-12 17:13:12 +03:00
Oleksii Holub 26322da800 [GUI] Notify when all exports are done instead of each of them separately 2019-04-12 16:24:17 +03:00
Oleksii Holub 1325edb135 Use latest C# version in all projects 2019-04-12 16:10:28 +03:00
Oleksii Holub dec2c26d83 Update appveyor image to vs2019 2019-04-12 16:01:01 +03:00
Oleksii Holub 82b0b8cb0a [GUI] Block UI when an export is underway 2019-04-12 15:23:07 +03:00
Oleksii Holub a7da90943f Remove trailing whitespace 2019-04-12 13:48:13 +03:00
Alexey Golub 1edeaf9d3a Update changelog 2019-04-12 01:03:42 +03:00
Alexey Golub 9c043376f9 Update version 2019-04-12 00:14:54 +03:00
Alexey Golub 77366cc9b4 Escape specific emojis in markdown
Fixes a part of #146
2019-04-12 00:11:01 +03:00
Alexey Golub ffdca7b799 [HTML] Fix multiple emojis not getting jumbo 2019-04-11 23:31:29 +03:00
Alexey Golub 30cba7959f Refactor string checks and fix exception when exporting multiple channels
Fix #164
2019-04-11 22:56:29 +03:00
52 changed files with 376 additions and 200 deletions
+25
View File
@@ -1,3 +1,28 @@
### v2.14 (15-Jun-2019)
- [TXT] Added support for embeds.
- [TXT] Added support for reactions.
- [CSV] Added support for reactions.
- [TXT] Changed how attachments are rendered.
### v2.13.1 (06-Jun-2019)
- Fixed an issue where the app sometimes crashed when exporting due to `System.InvalidCastException`.
- [HTML] Added `data-user-id` attribute to `span.chatlog__author-name`. The value of this attribute is author's Discord user ID.
### v2.13 (15-May-2019)
- Updated usage instructions.
- [HTML] Added BOT tags to messages posted by bots.
- [GUI] UI will now be disabled when exporting.
- [GUI] Notification informing that the export has completed will now appear after all exports have completed, instead of each of them separately.
### v2.12.1 (12-Apr-2019)
- [GUI] Fixed an issue where the app crashed when trying to export multiple channels.
- [HTML] Fixed an issue where some of the emojis were rendered via Twemoji while Discord renders them as plain text.
- [HTML] Fixed an issue where emojis weren't jumbo if there were more than one of them.
### v2.12 (11-Apr-2019)
- Improved markdown parsing performance which speeds up the final stage of exporting by around 4.5 times.
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>discordchatexporter</id>
<title>DiscordChatExporter</title>
<version>2.12</version>
<owners>Tyrrrz</owners>
<authors>Tyrrrz</authors>
<copyright>Copyright (C) Alexey Golub</copyright>
<summary>Exports Discord chat logs to a file</summary>
<description>DiscordChatExporter can be used to export message history from a Discord 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.</description>
<tags>discordchatexporter discord export chat logs archive gui</tags>
<projectSourceUrl>https://github.com/Tyrrrz/DiscordChatExporter</projectSourceUrl>
<packageSourceUrl>https://github.com/Tyrrrz/DiscordChatExporter</packageSourceUrl>
<projectUrl>http://tyrrrz.me/Projects/DiscordChatExporter</projectUrl>
<releaseNotes>https://github.com/Tyrrrz/DiscordChatExporter/blob/master/Changelog.md</releaseNotes>
<iconUrl>https://raw.githubusercontent.com/Tyrrrz/DiscordChatExporter/master/favicon.png</iconUrl>
<licenseUrl>https://raw.githubusercontent.com/Tyrrrz/DiscordChatExporter/master/License.txt</licenseUrl>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<bugTrackerUrl>https://github.com/Tyrrrz/DiscordChatExporter/issues</bugTrackerUrl>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>
-24
View File
@@ -1,24 +0,0 @@
$ErrorActionPreference = 'Stop'
$packageName = $env:ChocolateyPackageName
$installDirPath = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
# Install package (GUI)
$packageArgs = @{
packageName = $packageName
unzipLocation = $installDirPath
url = 'https://github.com/Tyrrrz/DiscordChatExporter/releases/download/2.12/DiscordChatExporter.zip'
}
Install-ChocolateyZipPackage @packageArgs
# Mark the executable as GUI
New-Item (Join-Path $installDirPath "DiscordChatExporter.exe.gui") -ItemType File -Force
# Install package (CLI)
$packageArgs = @{
packageName = $packageName
unzipLocation = $installDirPath
url = 'https://github.com/Tyrrrz/DiscordChatExporter/releases/download/2.12/DiscordChatExporter.CLI.zip'
}
Install-ChocolateyZipPackage @packageArgs
+6 -12
View File
@@ -1,4 +1,6 @@
# --- PORTABLE / GUI ---
New-Item "$PSScriptRoot\Portable\bin" -ItemType Directory -Force
# --- GUI ---
# Get files
$files = @()
@@ -6,10 +8,9 @@ $files += Get-Item -Path "$PSScriptRoot\..\License.txt"
$files += Get-ChildItem -Path "$PSScriptRoot\..\DiscordChatExporter.Gui\bin\Release\*" -Include "*.exe", "*.dll", "*.config"
# Pack into archive
New-Item "$PSScriptRoot\Portable\GUI\bin" -ItemType Directory -Force
$files | Compress-Archive -DestinationPath "$PSScriptRoot\Portable\GUI\bin\DiscordChatExporter.zip" -Force
$files | Compress-Archive -DestinationPath "$PSScriptRoot\Portable\bin\DiscordChatExporter.zip" -Force
# --- PORTABLE / CLI ---
# --- CLI ---
# Get files
$files = @()
@@ -17,11 +18,4 @@ $files += Get-Item -Path "$PSScriptRoot\..\License.txt"
$files += Get-ChildItem -Path "$PSScriptRoot\..\DiscordChatExporter.Cli\bin\Release\net46\*" -Include "*.exe", "*.dll", "*.config"
# Pack into archive
New-Item "$PSScriptRoot\Portable\CLI\bin" -ItemType Directory -Force
$files | Compress-Archive -DestinationPath "$PSScriptRoot\Portable\CLI\bin\DiscordChatExporter.CLI.zip" -Force
# --- CHOCOLATEY ---
# Create package
New-Item "$PSScriptRoot\Choco\bin\" -ItemType Directory -Force
choco pack $PSScriptRoot\Choco\discordchatexporter.nuspec --out $PSScriptRoot\Choco\bin\
$files | Compress-Archive -DestinationPath "$PSScriptRoot\Portable\bin\DiscordChatExporter.CLI.zip" -Force
@@ -3,17 +3,18 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net46;netcoreapp2.1</TargetFrameworks>
<Version>2.12</Version>
<Version>2.14</Version>
<Company>Tyrrrz</Company>
<Copyright>Copyright (c) Alexey Golub</Copyright>
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.3.0" />
<PackageReference Include="Stylet" Version="1.1.22" />
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.0" />
<PackageReference Include="Stylet" Version="1.2.0" />
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.2" />
</ItemGroup>
<ItemGroup>
+13 -5
View File
@@ -13,10 +13,10 @@ namespace DiscordChatExporter.Cli
Console.WriteLine("# To get user token:");
Console.WriteLine(" 1. Open Discord");
Console.WriteLine(" 2. Press Ctrl+Shift+I to show developer tools");
Console.WriteLine(" 3. Press Ctrl+R to reload");
Console.WriteLine(" 4. Navigate to the Application tab");
Console.WriteLine(" 5. Select \"Local Storage\" > \"https://discordapp.com\" on the left");
Console.WriteLine(" 6. Find \"token\" under key and copy the value");
Console.WriteLine(" 3. Navigate to the Application tab");
Console.WriteLine(" 4. Select \"Local Storage\" > \"https://discordapp.com\" on the left");
Console.WriteLine(" 5. Press Ctrl+R to reload");
Console.WriteLine(" 6. Find \"token\" at the bottom and copy the value");
Console.WriteLine();
Console.WriteLine("# To get bot token:");
Console.WriteLine(" 1. Go to Discord developer portal");
@@ -24,12 +24,20 @@ namespace DiscordChatExporter.Cli
Console.WriteLine(" 3. Navigate to the Bot section on the left");
Console.WriteLine(" 4. Under Token click Copy");
Console.WriteLine();
Console.WriteLine("# To get guild or channel ID:");
Console.WriteLine("# To get guild ID or guild channel ID:");
Console.WriteLine(" 1. Open Discord");
Console.WriteLine(" 2. Open Settings");
Console.WriteLine(" 3. Go to Appearance section");
Console.WriteLine(" 4. Enable Developer Mode");
Console.WriteLine(" 5. Right click on the desired guild or channel and click Copy ID");
Console.WriteLine();
Console.WriteLine("# To get direct message channel ID:");
Console.WriteLine(" 1. Open Discord");
Console.WriteLine(" 2. Open the desired direct message channel");
Console.WriteLine(" 3. Press Ctrl+Shift+I to show developer tools");
Console.WriteLine(" 4. Navigate to the Console tab");
Console.WriteLine(" 5. Type \"window.location.href\" and press Enter");
Console.WriteLine(" 6. Copy the first long sequence of numbers inside the URL");
}
public static void Main(string[] args)
@@ -24,7 +24,7 @@ namespace DiscordChatExporter.Cli.Verbs
var exportService = Container.Instance.Get<ExportService>();
// Configure settings
if (!Options.DateFormat.EmptyIfNull().IsWhiteSpace())
if (!Options.DateFormat.IsNullOrWhiteSpace())
settingsService.DateFormat = Options.DateFormat;
// Track progress
@@ -37,7 +37,7 @@ namespace DiscordChatExporter.Cli.Verbs
// Generate file path if not set or is a directory
var filePath = Options.OutputPath;
if (filePath.EmptyIfNull().IsWhiteSpace() || ExportHelper.IsDirectoryPath(filePath))
if (filePath.IsNullOrWhiteSpace() || ExportHelper.IsDirectoryPath(filePath))
{
// Generate default file name
var fileName = ExportHelper.GetDefaultExportFileName(Options.ExportFormat, chatLog.Guild,
@@ -27,7 +27,7 @@ namespace DiscordChatExporter.Cli.Verbs
var exportService = Container.Instance.Get<ExportService>();
// Configure settings
if (!Options.DateFormat.EmptyIfNull().IsWhiteSpace())
if (!Options.DateFormat.IsNullOrWhiteSpace())
settingsService.DateFormat = Options.DateFormat;
// Get channels
@@ -28,7 +28,7 @@ namespace DiscordChatExporter.Cli.Verbs
var exportService = Container.Instance.Get<ExportService>();
// Configure settings
if (!Options.DateFormat.EmptyIfNull().IsWhiteSpace())
if (!Options.DateFormat.IsNullOrWhiteSpace())
settingsService.DateFormat = Options.DateFormat;
// Get channels
@@ -8,7 +8,7 @@ namespace DiscordChatExporter.Cli.Verbs
{
public class GetDirectMessageChannelsVerb : Verb<GetDirectMessageChannelsOptions>
{
public GetDirectMessageChannelsVerb(GetDirectMessageChannelsOptions options)
public GetDirectMessageChannelsVerb(GetDirectMessageChannelsOptions options)
: base(options)
{
}
@@ -8,7 +8,7 @@ namespace DiscordChatExporter.Cli.Verbs
{
public class GetGuildsVerb : Verb<GetGuildsOptions>
{
public GetGuildsVerb(GetGuildsOptions options)
public GetGuildsVerb(GetGuildsOptions options)
: base(options)
{
}
@@ -2,10 +2,11 @@
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.0" />
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.2" />
</ItemGroup>
</Project>
@@ -1,6 +1,6 @@
namespace DiscordChatExporter.Core.Markdown.Internal
{
internal partial class ParsedMatch<T>
internal class ParsedMatch<T>
{
public int StartIndex { get; }
@@ -21,7 +21,7 @@ namespace DiscordChatExporter.Core.Markdown
// Capture any character until the earliest single asterisk not preceded or followed by an asterisk
// Opening asterisk must not be followed by whitespace
// Closing asterisk must not be preceeded by whitespace
// Closing asterisk must not be preceded by whitespace
private static readonly IMatcher<Node> ItalicFormattedNodeMatcher = new RegexMatcher<Node>(
new Regex("\\*(?!\\s)(.+?)(?<!\\s|\\*)\\*(?!\\*)", DefaultRegexOptions | RegexOptions.Singleline),
m => new FormattedNode(m.Value, "*", TextFormatting.Italic, Parse(m.Groups[1].Value)));
@@ -113,7 +113,7 @@ namespace DiscordChatExporter.Core.Markdown
// Capture <:lul:123456> or <a:lul:123456>
private static readonly IMatcher<Node> CustomEmojiNodeMatcher = new RegexMatcher<Node>(
new Regex("<(a)?:(.+?):(\\d+?)>", DefaultRegexOptions),
m => new EmojiNode(m.Value, m.Groups[3].Value, m.Groups[2].Value, !m.Groups[1].Value.IsEmpty()));
m => new EmojiNode(m.Value, m.Groups[3].Value, m.Groups[2].Value, !m.Groups[1].Value.IsNullOrWhiteSpace()));
/* Links */
@@ -140,13 +140,19 @@ namespace DiscordChatExporter.Core.Markdown
@"¯\_(ツ)_/¯",
s => new TextNode(s));
// Capture any "symbol/other" character or surrogate pair preceeded by a backslash
// Capture some specific emojis that don't get rendered
// This escapes it from matching for emoji
private static readonly IMatcher<Node> IgnoredEmojiTextNodeMatcher = new RegexMatcher<Node>(
new Regex("(\\u26A7|\\u2640|\\u2642|\\u2695|\\u267E|\\u00A9|\\u00AE|\\u2122)", DefaultRegexOptions),
m => new TextNode(m.Value, m.Groups[1].Value));
// Capture any "symbol/other" character or surrogate pair preceded by a backslash
// This escapes it from matching for emoji
private static readonly IMatcher<Node> EscapedSymbolTextNodeMatcher = new RegexMatcher<Node>(
new Regex("\\\\(\\p{So}|\\p{Cs}{2})", DefaultRegexOptions),
m => new TextNode(m.Value, m.Groups[1].Value));
// Capture any non-whitespace, non latin alphanumeric character preceeded by a backslash
// Capture any non-whitespace, non latin alphanumeric character preceded by a backslash
// This escapes it from matching for formatting or other tokens
private static readonly IMatcher<Node> EscapedCharacterTextNodeMatcher = new RegexMatcher<Node>(
new Regex("\\\\([^a-zA-Z0-9\\s])", DefaultRegexOptions),
@@ -155,6 +161,13 @@ namespace DiscordChatExporter.Core.Markdown
// Combine all matchers into one
// Matchers that have similar patterns are ordered from most specific to least specific
private static readonly IMatcher<Node> AggregateNodeMatcher = new AggregateMatcher<Node>(
// Escaped text
ShrugTextNodeMatcher,
IgnoredEmojiTextNodeMatcher,
EscapedSymbolTextNodeMatcher,
EscapedCharacterTextNodeMatcher,
// Formatting
ItalicBoldFormattedNodeMatcher,
ItalicUnderlineFormattedNodeMatcher,
BoldFormattedNodeMatcher,
@@ -163,21 +176,26 @@ namespace DiscordChatExporter.Core.Markdown
ItalicAltFormattedNodeMatcher,
StrikethroughFormattedNodeMatcher,
SpoilerFormattedNodeMatcher,
// Code blocks
MultilineCodeBlockNodeMatcher,
InlineCodeBlockNodeMatcher,
// Mentions
EveryoneMentionNodeMatcher,
HereMentionNodeMatcher,
UserMentionNodeMatcher,
ChannelMentionNodeMatcher,
RoleMentionNodeMatcher,
StandardEmojiNodeMatcher,
CustomEmojiNodeMatcher,
// Links
TitledLinkNodeMatcher,
AutoLinkNodeMatcher,
HiddenLinkNodeMatcher,
ShrugTextNodeMatcher,
EscapedSymbolTextNodeMatcher,
EscapedCharacterTextNodeMatcher);
// Emoji
StandardEmojiNodeMatcher,
CustomEmojiNodeMatcher);
private static IReadOnlyList<Node> Parse(string input, IMatcher<Node> matcher) =>
matcher.MatchAll(input, s => new TextNode(s)).Select(r => r.Value).ToArray();
@@ -1,4 +1,6 @@
namespace DiscordChatExporter.Core.Markdown.Nodes
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Markdown.Nodes
{
public class EmojiNode : Node
{
@@ -8,7 +10,7 @@
public bool IsAnimated { get; }
public bool IsCustomEmoji => Id != null;
public bool IsCustomEmoji => !Id.IsNullOrWhiteSpace();
public EmojiNode(string source, string id, string name, bool isAnimated)
: base(source)
+1 -1
View File
@@ -15,7 +15,7 @@
public string Topic { get; }
public ChannelType Type { get; }
public Channel(string id, string parentId, string guildId, string name, string topic, ChannelType type)
{
Id = id;
@@ -2,10 +2,11 @@
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.0" />
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.2" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -40,7 +40,7 @@ namespace DiscordChatExporter.Core.Models
public static string GetImageUrl(string id, string name, bool isAnimated)
{
// Custom emoji
if (id != null)
if (!id.IsNullOrWhiteSpace())
{
// Animated
if (isAnimated)
+4 -2
View File
@@ -1,4 +1,6 @@
namespace DiscordChatExporter.Core.Models
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Models
{
// https://discordapp.com/developers/docs/resources/guild#guild-object
@@ -28,7 +30,7 @@
{
public static string GetIconUrl(string id, string iconHash)
{
return iconHash != null
return !iconHash.IsNullOrWhiteSpace()
? $"https://cdn.discordapp.com/icons/{id}/{iconHash}.png"
: "https://cdn.discordapp.com/embed/avatars/0.png";
}
+7 -3
View File
@@ -1,4 +1,5 @@
using System;
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Models
{
@@ -18,12 +19,15 @@ namespace DiscordChatExporter.Core.Models
public string AvatarUrl { get; }
public User(string id, int discriminator, string name, string avatarHash)
public bool IsBot { get; }
public User(string id, int discriminator, string name, string avatarHash, bool isBot)
{
Id = id;
Discriminator = discriminator;
Name = name;
AvatarHash = avatarHash;
IsBot = isBot;
FullName = GetFullName(name, discriminator);
AvatarUrl = GetAvatarUrl(id, discriminator, avatarHash);
@@ -39,7 +43,7 @@ namespace DiscordChatExporter.Core.Models
public static string GetAvatarUrl(string id, int discriminator, string avatarHash)
{
// Custom avatar
if (avatarHash != null)
if (!avatarHash.IsNullOrWhiteSpace())
{
// Animated
if (avatarHash.StartsWith("a_", StringComparison.Ordinal))
@@ -53,6 +57,6 @@ namespace DiscordChatExporter.Core.Models
return $"https://cdn.discordapp.com/embed/avatars/{discriminator % 5}.png";
}
public static User CreateUnknownUser(string id) => new User(id, 0, "Unknown", null);
public static User CreateUnknownUser(string id) => new User(id, 0, "Unknown", null, false);
}
}
@@ -96,6 +96,10 @@ namespace DiscordChatExporter.Core.Rendering
var formattedAttachments = message.Attachments.Select(a => a.Url).JoinToString(",");
await RenderFieldAsync(writer, formattedAttachments);
// Reactions
var formattedReactions = message.Reactions.Select(r => r.Emoji.Name + $"({r.Count})").JoinToString(",");
await RenderFieldAsync(writer, formattedReactions);
// Line break
await writer.WriteLineAsync();
}
@@ -103,7 +107,7 @@ namespace DiscordChatExporter.Core.Rendering
public async Task RenderAsync(TextWriter writer)
{
// Headers
await writer.WriteLineAsync("Author;Date;Content;Attachments;");
await writer.WriteLineAsync("Author;Date;Content;Attachments;Reactions;");
// Log
foreach (var message in _chatLog.Messages)
@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
@@ -14,7 +15,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Scriban" Version="2.0.0" />
<PackageReference Include="Scriban" Version="2.0.1" />
</ItemGroup>
<ItemGroup>
@@ -1,6 +1,6 @@
using DiscordChatExporter.Core.Models;
using System;
using System;
using System.Collections.Generic;
using DiscordChatExporter.Core.Models;
namespace DiscordChatExporter.Core.Rendering
{
@@ -1,8 +1,8 @@
using Scriban.Parsing;
using Scriban.Runtime;
using Scriban;
using System.Reflection;
using System.Reflection;
using System.Threading.Tasks;
using Scriban;
using Scriban.Parsing;
using Scriban.Runtime;
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Rendering
@@ -46,7 +46,7 @@ namespace DiscordChatExporter.Core.Rendering
return true;
}).Select(g => new MessageGroup(g.First().Author, g.First().Timestamp, g));
private string FormatMarkdown(Node node, bool isTopLevel, bool isSingle)
private string FormatMarkdown(Node node, bool isJumbo)
{
// Text node
if (node is TextNode textNode)
@@ -92,7 +92,7 @@ namespace DiscordChatExporter.Core.Rendering
if (node is MultilineCodeBlockNode multilineCodeBlockNode)
{
// Set language class for syntax highlighting
var languageCssClass = multilineCodeBlockNode.Language != null
var languageCssClass = !multilineCodeBlockNode.Language.IsNullOrWhiteSpace()
? "language-" + multilineCodeBlockNode.Language
: null;
@@ -136,8 +136,8 @@ namespace DiscordChatExporter.Core.Rendering
// Get emoji image URL
var emojiImageUrl = Emoji.GetImageUrl(emojiNode.Id, emojiNode.Name, emojiNode.IsAnimated);
// Emoji can be jumboable if it's the only top-level node
var jumboableCssClass = isTopLevel && isSingle ? "emoji--large" : null;
// Make emoji large if it's jumbo
var jumboableCssClass = isJumbo ? "emoji--large" : null;
return $"<img class=\"emoji {jumboableCssClass}\" alt=\"{emojiNode.Name}\" title=\"{emojiNode.Name}\" src=\"{emojiImageUrl}\" />";
}
@@ -154,8 +154,10 @@ namespace DiscordChatExporter.Core.Rendering
private string FormatMarkdown(IReadOnlyList<Node> nodes, bool isTopLevel)
{
var isSingle = nodes.Count == 1;
return nodes.Select(n => FormatMarkdown(n, isTopLevel, isSingle)).JoinToString("");
// Emojis are jumbo if all top-level nodes are emoji nodes, disregarding whitespace
var isJumbo = isTopLevel && nodes.Where(n => !n.Source.IsNullOrWhiteSpace()).All(n => n is EmojiNode);
return nodes.Select(n => FormatMarkdown(n, isJumbo)).JoinToString("");
}
private string FormatMarkdown(string markdown) => FormatMarkdown(MarkdownParser.Parse(markdown), true);
@@ -93,6 +93,90 @@ namespace DiscordChatExporter.Core.Rendering
private string FormatMarkdown(string markdown) => FormatMarkdown(MarkdownParser.Parse(markdown));
private async Task RenderAttachmentsAsync(TextWriter writer, IReadOnlyList<Attachment> attachments)
{
if (attachments.Any())
{
await writer.WriteLineAsync("{Attachments}");
foreach (var attachment in attachments)
await writer.WriteLineAsync(attachment.Url);
await writer.WriteLineAsync();
}
}
private async Task RenderEmbedsAsync(TextWriter writer, IReadOnlyList<Embed> embeds)
{
foreach (var embed in embeds)
{
await writer.WriteLineAsync("{Embed}");
// Author name
if (!(embed.Author?.Name).IsNullOrWhiteSpace())
await writer.WriteLineAsync(embed.Author?.Name);
// URL
if (!embed.Url.IsNullOrWhiteSpace())
await writer.WriteLineAsync(embed.Url);
// Title
if (!embed.Title.IsNullOrWhiteSpace())
await writer.WriteLineAsync(FormatMarkdown(embed.Title));
// Description
if (!embed.Description.IsNullOrWhiteSpace())
await writer.WriteLineAsync(FormatMarkdown(embed.Description));
// Fields
foreach (var field in embed.Fields)
{
// Name
if (!field.Name.IsNullOrWhiteSpace())
await writer.WriteLineAsync(field.Name);
// Value
if (!field.Value.IsNullOrWhiteSpace())
await writer.WriteLineAsync(field.Value);
}
// Thumbnail URL
if (!(embed.Thumbnail?.Url).IsNullOrWhiteSpace())
await writer.WriteLineAsync(embed.Thumbnail?.Url);
// Image URL
if (!(embed.Image?.Url).IsNullOrWhiteSpace())
await writer.WriteLineAsync(embed.Image?.Url);
// Footer text
if (!(embed.Footer?.Text).IsNullOrWhiteSpace())
await writer.WriteLineAsync(embed.Footer?.Text);
await writer.WriteLineAsync();
}
}
private async Task RenderReactionsAsync(TextWriter writer, IReadOnlyList<Reaction> reactions)
{
if (reactions.Any())
{
await writer.WriteLineAsync("{Reactions}");
foreach (var reaction in reactions)
{
await writer.WriteAsync(reaction.Emoji.Name);
if (reaction.Count > 1)
await writer.WriteAsync($" ({reaction.Count})");
await writer.WriteAsync(" ");
}
await writer.WriteLineAsync();
await writer.WriteLineAsync();
}
}
private async Task RenderMessageAsync(TextWriter writer, Message message)
{
// Timestamp and author
@@ -101,9 +185,17 @@ namespace DiscordChatExporter.Core.Rendering
// Content
await writer.WriteLineAsync(FormatMarkdown(message.Content));
// Separator
await writer.WriteLineAsync();
// Attachments
foreach (var attachment in message.Attachments)
await writer.WriteLineAsync(attachment.Url);
await RenderAttachmentsAsync(writer, message.Attachments);
// Embeds
await RenderEmbedsAsync(writer, message.Embeds);
// Reactions
await RenderReactionsAsync(writer, message.Reactions);
}
public async Task RenderAsync(TextWriter writer)
@@ -120,10 +212,7 @@ namespace DiscordChatExporter.Core.Rendering
// Log
foreach (var message in _chatLog.Messages)
{
await RenderMessageAsync(writer, message);
await writer.WriteLineAsync();
}
}
}
}
@@ -1,5 +1,35 @@
/* === GENERAL === */
@font-face {
font-family: Whitney;
src: url(https://discordapp.com/assets/6c6374bad0b0b6d204d8d6dc4a18d820.woff);
font-weight: 300;
}
@font-face {
font-family: Whitney;
src: url(https://discordapp.com/assets/e8acd7d9bf6207f99350ca9f9e23b168.woff);
font-weight: 400;
}
@font-face {
font-family: Whitney;
src: url(https://discordapp.com/assets/3bdef1251a424500c1b3a78dea9b7e57.woff);
font-weight: 500;
}
@font-face {
font-family: Whitney;
src: url(https://discordapp.com/assets/be0060dafb7a0e31d2a1ca17c0708636.woff);
font-weight: 600;
}
@font-face {
font-family: Whitney;
src: url(https://discordapp.com/assets/8e12fb4f14d9c4592eb8ec9f22337b04.woff);
font-weight: 700;
}
body {
font-family: "Whitney", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 16px;
@@ -41,6 +71,7 @@ img {
.pre--inline {
padding: 2px;
border-radius: 3px;
font-size: 85%;
}
.mention {
@@ -289,7 +320,7 @@ img {
}
.chatlog__embed-footer-text {
font-weight: 600;
font-weight: 500;
font-size: .75em;
}
@@ -309,4 +340,17 @@ img {
min-width: 9px;
margin-left: 6px;
font-size: .875em;
}
.chatlog__bot-tag {
margin-left: 0.3em;
background: #7289da;
color: #ffffff;
font-size: 0.625em;
padding: 1px 2px;
border-radius: 3px;
vertical-align: middle;
line-height: 1.3;
position: relative;
top: -.2em;
}
@@ -67,7 +67,10 @@
</div>
<div class="chatlog__messages">
{{~ # Author name and timestamp ~}}
<span class="chatlog__author-name" title="{{ group.Author.FullName | html.escape }}">{{ group.Author.Name | html.escape }}</span>
<span class="chatlog__author-name" title="{{ group.Author.FullName | html.escape }}" data-user-id="{{ group.Author.Id | html.escape }}">{{ group.Author.Name | html.escape }}</span>
{{~ if group.Author.IsBot ~}}
<span class="chatlog__bot-tag">BOT</span>
{{~ end ~}}
<span class="chatlog__timestamp">{{ group.Timestamp | FormatDate | html.escape }}</span>
{{~ # Messages ~}}
@@ -16,8 +16,9 @@ namespace DiscordChatExporter.Core.Services
var discriminator = json["discriminator"].Value<int>();
var name = json["username"].Value<string>();
var avatarHash = json["avatar"].Value<string>();
var isBot = json["bot"]?.Value<bool>() ?? false;
return new User(id, discriminator, name, avatarHash);
return new User(id, discriminator, name, avatarHash, isBot);
}
private Guild ParseGuild(JToken json)
@@ -41,14 +42,14 @@ namespace DiscordChatExporter.Core.Services
var guildId = json["guild_id"]?.Value<string>();
// If the guild ID is blank, it's direct messages
if (guildId == null)
if (guildId.IsNullOrWhiteSpace())
guildId = Guild.DirectMessages.Id;
// Try to extract name
var name = json["name"]?.Value<string>();
// If the name is blank, it's direct messages
if (name == null)
if (name.IsNullOrWhiteSpace())
name = json["recipients"].Select(ParseUser).Select(u => u.Name).JoinToString(", ");
return new Channel(id, parentId, guildId, name, topic, type);
@@ -117,7 +118,7 @@ namespace DiscordChatExporter.Core.Services
var title = json["title"]?.Value<string>();
var description = json["description"]?.Value<string>();
var url = json["url"]?.Value<string>();
var timestamp = json["timestamp"]?.Value<DateTimeOffset>();
var timestamp = json["timestamp"]?.Value<DateTime>().ToDateTimeOffset();
// Get color
var color = json["color"] != null
@@ -164,8 +165,8 @@ namespace DiscordChatExporter.Core.Services
// Get basic data
var id = json["id"].Value<string>();
var channelId = json["channel_id"].Value<string>();
var timestamp = json["timestamp"].Value<DateTimeOffset>();
var editedTimestamp = json["edited_timestamp"]?.Value<DateTimeOffset?>();
var timestamp = json["timestamp"].Value<DateTime>().ToDateTimeOffset();
var editedTimestamp = json["edited_timestamp"]?.Value<DateTime?>()?.ToDateTimeOffset();
var content = json["content"].Value<string>();
var type = (MessageType) json["type"].Value<int>();
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -9,7 +8,6 @@ using DiscordChatExporter.Core.Models;
using DiscordChatExporter.Core.Services.Exceptions;
using DiscordChatExporter.Core.Services.Internal;
using Failsafe;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Tyrrrz.Extensions;
@@ -48,7 +46,7 @@ namespace DiscordChatExporter.Core.Services
var value = parameter.SubstringAfter("=");
// Skip empty values
if (value.IsEmpty())
if (value.IsNullOrWhiteSpace())
continue;
request.RequestUri = request.RequestUri.SetQueryParameter(key, value);
@@ -66,12 +64,7 @@ namespace DiscordChatExporter.Core.Services
var raw = await response.Content.ReadAsStringAsync();
// Parse
using (var reader = new JsonTextReader(new StringReader(raw)))
{
reader.DateParseHandling = DateParseHandling.DateTimeOffset;
return JToken.Load(reader);
}
return JToken.Parse(raw);
}
}
});
@@ -2,13 +2,14 @@
<PropertyGroup>
<TargetFrameworks>net46;netstandard2.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Failsafe" Version="1.1.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="Onova" Version="2.4.2" />
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="Onova" Version="2.4.3" />
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.2" />
<PackageReference Include="Tyrrrz.Settings" Version="1.3.4" />
</ItemGroup>
@@ -38,7 +38,7 @@ namespace DiscordChatExporter.Core.Services
{
// Create output directory
var dirPath = Path.GetDirectoryName(filePath);
if (!dirPath.EmptyIfNull().IsWhiteSpace())
if (!dirPath.IsNullOrWhiteSpace())
Directory.CreateDirectory(dirPath);
// Render chat log to output file
@@ -74,7 +74,7 @@ namespace DiscordChatExporter.Core.Services
var partitionFilePath = $"{fileNameWithoutExt} [{partitionNumber} of {partitions.Length}]{fileExt}";
// Compose full file path
if (!dirPath.EmptyIfNull().IsWhiteSpace())
if (!dirPath.IsNullOrWhiteSpace())
partitionFilePath = Path.Combine(dirPath, partitionFilePath);
// Export
@@ -3,6 +3,7 @@ using System.IO;
using System.Linq;
using System.Text;
using DiscordChatExporter.Core.Models;
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Services.Helpers
{
@@ -11,7 +12,7 @@ namespace DiscordChatExporter.Core.Services.Helpers
public static bool IsDirectoryPath(string path) =>
path.Last() == Path.DirectorySeparatorChar ||
path.Last() == Path.AltDirectorySeparatorChar ||
Path.GetExtension(path) == null;
(Path.GetExtension(path).IsNullOrWhiteSpace() && !File.Exists(path));
public static string GetDefaultExportFileName(ExportFormat format, Guild guild, Channel channel,
DateTimeOffset? after = null, DateTimeOffset? before = null)
@@ -5,11 +5,11 @@ namespace DiscordChatExporter.Core.Services.Internal
{
internal static class Extensions
{
public static string ToSnowflake(this DateTimeOffset date)
public static DateTimeOffset ToDateTimeOffset(this DateTime dateTime) => new DateTimeOffset(dateTime);
public static string ToSnowflake(this DateTimeOffset dateTime)
{
const long epoch = 62135596800000;
var unixTime = date.ToUniversalTime().Ticks / TimeSpan.TicksPerMillisecond - epoch;
var value = ((ulong) unixTime - 1420070400000UL) << 22;
var value = ((ulong) dateTime.ToUnixTimeMilliseconds() - 1420070400000UL) << 22;
return value.ToString();
}
+1 -1
View File
@@ -1,6 +1,6 @@
namespace DiscordChatExporter.Gui
{
public partial class App
{
{
}
}
@@ -11,18 +11,18 @@ namespace DiscordChatExporter.Gui.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTimeOffset date)
return date.DateTime;
if (value is DateTimeOffset dateTimeOffsetValue)
return dateTimeOffsetValue.DateTime;
return null;
return default;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is DateTime date)
return new DateTimeOffset(date);
if (value is DateTime dateTimeValue)
return new DateTimeOffset(dateTimeValue);
return null;
return default;
}
}
}
@@ -12,10 +12,10 @@ namespace DiscordChatExporter.Gui.Converters
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ExportFormat format)
return format.GetDisplayName();
if (value is ExportFormat exportFormatValue)
return exportFormatValue.GetDisplayName();
return null;
return default;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace DiscordChatExporter.Gui.Converters
{
[ValueConversion(typeof(bool), typeof(bool))]
public class InverseBoolConverter : IValueConverter
{
public static InverseBoolConverter Instance { get; } = new InverseBoolConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return default;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return default;
}
}
}
@@ -13,6 +13,7 @@
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<LangVersion>latest</LangVersion>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
@@ -61,6 +62,8 @@
<Compile Include="Bootstrapper.cs" />
<Compile Include="Converters\DateTimeOffsetToDateTimeConverter.cs" />
<Compile Include="Converters\ExportFormatToStringConverter.cs" />
<Compile Include="Converters\InverseBoolConverter.cs" />
<Compile Include="Services\UpdateService.cs" />
<Compile Include="ViewModels\Components\ChannelViewModel.cs" />
<Compile Include="ViewModels\Components\GuildViewModel.cs" />
<Compile Include="ViewModels\Dialogs\ExportSetupViewModel.cs" />
@@ -127,28 +130,28 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Gress">
<Version>1.0.2</Version>
<Version>1.1.1</Version>
</PackageReference>
<PackageReference Include="MaterialDesignColors">
<Version>1.1.3</Version>
</PackageReference>
<PackageReference Include="MaterialDesignThemes">
<Version>2.5.0.1205</Version>
<Version>2.5.1</Version>
</PackageReference>
<PackageReference Include="Ookii.Dialogs.Wpf">
<Version>1.0.0</Version>
<Version>1.1.0</Version>
</PackageReference>
<PackageReference Include="PropertyChanged.Fody">
<Version>2.6.0</Version>
<Version>2.6.1</Version>
</PackageReference>
<PackageReference Include="Stylet">
<Version>1.1.22</Version>
<Version>1.2.0</Version>
</PackageReference>
<PackageReference Include="System.Windows.Interactivity.WPF">
<Version>2.0.20525</Version>
</PackageReference>
<PackageReference Include="Tyrrrz.Extensions">
<Version>1.6.0</Version>
<Version>1.6.2</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+8 -3
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuild. -->
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
@@ -36,12 +36,17 @@
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification on the target assembly after all weavers have been finished.</xs:documentation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
@@ -3,5 +3,5 @@
[assembly: AssemblyTitle("DiscordChatExporter")]
[assembly: AssemblyCompany("Tyrrrz")]
[assembly: AssemblyCopyright("Copyright (c) Alexey Golub")]
[assembly: AssemblyVersion("2.12")]
[assembly: AssemblyFileVersion("2.12")]
[assembly: AssemblyVersion("2.14")]
[assembly: AssemblyFileVersion("2.14")]
@@ -1,10 +1,11 @@
using System;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Services;
using Onova;
using Onova.Exceptions;
using Onova.Services;
namespace DiscordChatExporter.Core.Services
namespace DiscordChatExporter.Gui.Services
{
public class UpdateService : IDisposable
{
@@ -6,6 +6,7 @@ using DiscordChatExporter.Core.Services;
using DiscordChatExporter.Core.Services.Helpers;
using DiscordChatExporter.Gui.ViewModels.Components;
using DiscordChatExporter.Gui.ViewModels.Framework;
using Tyrrrz.Extensions;
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
{
@@ -84,7 +85,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
}
// If canceled - return
if (OutputPath == null)
if (OutputPath.IsNullOrWhiteSpace())
return;
// Close dialog
@@ -6,7 +6,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
{
public T DialogResult { get; private set; }
public void Close(T dialogResult = default(T))
public void Close(T dialogResult = default)
{
// Set the result
DialogResult = dialogResult;
@@ -21,6 +21,6 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
}
public abstract class DialogScreen : DialogScreen<bool?>
{
{
}
}
@@ -39,6 +39,6 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
public static ExportSetupViewModel CreateExportSetupViewModel(this IViewModelFactory factory,
GuildViewModel guild, ChannelViewModel channel)
=> factory.CreateExportSetupViewModel(guild, new[] {channel});
=> factory.CreateExportSetupViewModel(guild, new[] { channel });
}
}
@@ -8,6 +8,7 @@ using DiscordChatExporter.Core.Models;
using DiscordChatExporter.Core.Services;
using DiscordChatExporter.Core.Services.Exceptions;
using DiscordChatExporter.Core.Services.Helpers;
using DiscordChatExporter.Gui.Services;
using DiscordChatExporter.Gui.ViewModels.Components;
using DiscordChatExporter.Gui.ViewModels.Framework;
using Gress;
@@ -122,7 +123,7 @@ namespace DiscordChatExporter.Gui.ViewModels
await _dialogManager.ShowDialogAsync(dialog);
}
public bool CanPopulateGuildsAndChannels => !IsBusy && !TokenValue.EmptyIfNull().IsWhiteSpace();
public bool CanPopulateGuildsAndChannels => !IsBusy && !TokenValue.IsNullOrWhiteSpace();
public async void PopulateGuildsAndChannels()
{
@@ -235,7 +236,7 @@ namespace DiscordChatExporter.Gui.ViewModels
}
}
public bool CanExportChannels => !IsBusy && SelectedChannels.EmptyIfNull().Any();
public bool CanExportChannels => !IsBusy && !SelectedChannels.IsNullOrEmpty();
public async void ExportChannels()
{
@@ -253,6 +254,7 @@ namespace DiscordChatExporter.Gui.ViewModels
var operations = ProgressManager.CreateOperations(dialog.Channels.Count);
// Export channels
var successfulExportCount = 0;
for (var i = 0; i < dialog.Channels.Count; i++)
{
// Get operation and channel
@@ -274,15 +276,15 @@ namespace DiscordChatExporter.Gui.ViewModels
}
// Get chat log
var chatLog = await _dataService.GetChatLogAsync(token, dialog.Guild, channel,
var chatLog = await _dataService.GetChatLogAsync(token, dialog.Guild, channel,
dialog.After, dialog.Before, operation);
// Export
await _exportService.ExportChatLogAsync(chatLog, filePath, dialog.SelectedFormat,
dialog.PartitionLimit);
// Notify completion
Notifications.Enqueue($"Channel [{channel.Model.Name}] successfully exported");
// Report successful export
successfulExportCount++;
}
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
@@ -298,6 +300,9 @@ namespace DiscordChatExporter.Gui.ViewModels
operation.Dispose();
}
}
// Notify of overall completion
Notifications.Enqueue($"Successfully exported {successfulExportCount} channel(s)");
}
}
}
+9 -8
View File
@@ -3,6 +3,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="clr-namespace:DiscordChatExporter.Gui.Behaviors"
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
@@ -124,7 +125,7 @@
Value="{Binding ProgressManager.Progress, Mode=OneWay}" />
<!-- Content -->
<Grid Grid.Row="2">
<Grid Grid.Row="2" IsEnabled="{Binding IsBusy, Converter={x:Static converters:InverseBoolConverter.Instance}}">
<!-- Placeholder / usage instructions -->
<Grid Margin="32,32,8,8" Visibility="{Binding AvailableGuilds, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}">
<!-- For user token -->
@@ -137,23 +138,23 @@
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+Shift+I" />
<Run Text="to show developer tools" />
<LineBreak />
<Run Text="3. Press" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+R" />
<Run Text="to reload" />
<LineBreak />
<Run Text="4. Navigate to the" />
<Run Text="3. Navigate to the" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Application" />
<Run Text="tab" />
<LineBreak />
<Run Text="5. Select" />
<Run Text="4. Select" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Local Storage" />
<Run Text="&gt;" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="https://discordapp.com" />
<Run Text="on the left" />
<LineBreak />
<Run Text="5. Press" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+R" />
<Run Text="to reload" />
<LineBreak />
<Run Text="6. Find" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="token" />
<Run Text="under key and copy the value" />
<Run Text="at the bottom and copy the value" />
</TextBlock>
<TextBlock Margin="0,24,0,0" FontSize="14">
<Run Text="To authorize using bot token instead, click" />
@@ -4,7 +4,7 @@
{
public RootView()
{
InitializeComponent();
InitializeComponent();
}
}
}
-1
View File
@@ -11,7 +11,6 @@ DiscordChatExporter can be used to export message history from a [Discord](https
## Download
- [Stable releases](https://github.com/Tyrrrz/DiscordChatExporter/releases)
- [Chocolatey](https://chocolatey.org/packages/DiscordChatExporter): `choco install discordchatexporter`
- [Docker](https://hub.docker.com/r/tyrrrz/discordchatexporter): `docker pull tyrrrz/discordchatexporter`
- [Continuous integration](https://ci.appveyor.com/project/Tyrrrz/DiscordChatExporter)
+1 -3
View File
@@ -9,6 +9,4 @@ function Replace-TextInFile {
}
Replace-TextInFile "$PSScriptRoot\DiscordChatExporter.Cli\DiscordChatExporter.Cli.csproj" '(?<=<Version>)(.*?)(?=</Version>)' $newVersion
Replace-TextInFile "$PSScriptRoot\DiscordChatExporter.Gui\Properties\AssemblyInfo.cs" '(?<=Assembly.*?Version\(")(.*?)(?="\)\])' $newVersion
Replace-TextInFile "$PSScriptRoot\Deploy\Choco\discordchatexporter.nuspec" '(?<=<version>)(.*?)(?=</version>)' $newVersion
Replace-TextInFile "$PSScriptRoot\Deploy\Choco\tools\chocolateyinstall.ps1" '(?<=download/)(.*?)(?=/)' $newVersion
Replace-TextInFile "$PSScriptRoot\DiscordChatExporter.Gui\Properties\AssemblyInfo.cs" '(?<=Assembly.*?Version\(")(.*?)(?="\)\])' $newVersion
+3 -14
View File
@@ -1,6 +1,6 @@
version: '{build}'
image: Visual Studio 2017
image: Visual Studio 2019 Preview
configuration: Release
before_build:
@@ -13,12 +13,10 @@ after_build:
- ps: Deploy\Prepare.ps1
artifacts:
- path: Deploy\Portable\GUI\bin\DiscordChatExporter.zip
- path: Deploy\Portable\bin\DiscordChatExporter.zip
name: DiscordChatExporter.zip
- path: Deploy\Portable\CLI\bin\DiscordChatExporter.CLI.zip
- path: Deploy\Portable\bin\DiscordChatExporter.CLI.zip
name: DiscordChatExporter.CLI.zip
- path: Deploy\Choco\bin\DiscordChatExporter*.nupkg
name: DiscordChatExporter-Choco.nupkg
deploy:
- provider: GitHub
@@ -26,15 +24,6 @@ deploy:
secure: sjQHWRw29AMiVMn3MtidtWnAzAf1mJ+mkJ/7h1B9TIAHhkFrqwMK7LtXV+uNJ9AO
artifact: DiscordChatExporter.zip,DiscordChatExporter.CLI.zip
description: '[Changelog](https://github.com/Tyrrrz/DiscordChatExporter/blob/master/Changelog.md)'
on:
branch: master
appveyor_repo_tag: true
- provider: NuGet
server: https://push.chocolatey.org/
api_key:
secure: aQVQ50i8GcwxMXTHf2ck5PijnOeVZY8Z9MEROQTvT49/7/az9w8ru8NCyEsV6H5I
artifact: DiscordChatExporter-Choco.nupkg
skip_symbols: true
on:
branch: master
appveyor_repo_tag: true
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB