mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-07-08 15:14:37 +02:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b518fb2f59 | |||
| 94165dcd8d | |||
| d6507598fa | |||
| a0359b1e43 | |||
| aa53cecd4e | |||
| 4144911208 | |||
| d28e81f8dc | |||
| 761cb032d7 | |||
| 47f0561c71 | |||
| 23116b776b | |||
| ba27a9f909 | |||
| 52684c264f | |||
| bcad5b4ed1 | |||
| 95a4217ab3 | |||
| 4e8fb80ac5 | |||
| 7ae560dde0 |
+11
-2
@@ -1,9 +1,18 @@
|
|||||||
|
### v2.8 (03-Nov-2018)
|
||||||
|
|
||||||
|
- Added support for partitioning which lets you split the output into multiple files by setting the partition message limit (`-p` parameter for CLI).
|
||||||
|
- Exported file will no longer open automatically on completion.
|
||||||
|
- Reduced amount of memory used during exporting.
|
||||||
|
- Disabled message grouping in PlainText export.
|
||||||
|
- Improved encoding of newlines in CSV export.
|
||||||
|
- Improved error messages in the GUI app during crashes.
|
||||||
|
|
||||||
### v2.7 (14-Sep-2018)
|
### v2.7 (14-Sep-2018)
|
||||||
|
|
||||||
- Updated instructions on how to obtain the user token.
|
- Updated instructions on how to obtain the user token.
|
||||||
- Expanded CLI with new commands: `channels` (get a list of channels in a guild), `dms` (get a list of DM channels), `guilds` (get a list of guilds), on top of `export` (export chatlog).
|
- Expanded CLI with new commands: `channels` (get a list of channels in a guild), `dm` (get a list of DM channels), `guilds` (get a list of guilds), on top of `export` (export chatlog).
|
||||||
- Improved help text and error messages in CLI.
|
- Improved help text and error messages in CLI.
|
||||||
- In CLI, if the provided output file path is a directory, a file name will be generated and appended to it automatically.
|
- In CLI, the file name will be automatically generated if the provided output file path is a directory.
|
||||||
|
|
||||||
### v2.6 (25-Jul-2018)
|
### v2.6 (25-Jul-2018)
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ namespace DiscordChatExporter.Cli
|
|||||||
// 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<ISettingsService, SettingsService>();
|
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
||||||
SimpleIoc.Default.Register<IUpdateService, UpdateService>();
|
SimpleIoc.Default.Register<IUpdateService, UpdateService>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net461</TargetFramework>
|
<TargetFramework>net461</TargetFramework>
|
||||||
<Version>2.7</Version>
|
<Version>2.8</Version>
|
||||||
<Company>Tyrrrz</Company>
|
<Company>Tyrrrz</Company>
|
||||||
<Copyright>Copyright (c) 2017-2018 Alexey Golub</Copyright>
|
<Copyright>Copyright (c) 2017-2018 Alexey Golub</Copyright>
|
||||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||||
|
|||||||
@@ -43,8 +43,7 @@ namespace DiscordChatExporter.Cli
|
|||||||
typeof(ExportChatOptions),
|
typeof(ExportChatOptions),
|
||||||
typeof(GetChannelsOptions),
|
typeof(GetChannelsOptions),
|
||||||
typeof(GetDirectMessageChannelsOptions),
|
typeof(GetDirectMessageChannelsOptions),
|
||||||
typeof(GetGuildsOptions),
|
typeof(GetGuildsOptions)
|
||||||
//typeof(UpdateAppOptions)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
@@ -55,7 +54,6 @@ namespace DiscordChatExporter.Cli
|
|||||||
parsedArgs.WithParsed<GetChannelsOptions>(o => new GetChannelsVerb(o).Execute());
|
parsedArgs.WithParsed<GetChannelsOptions>(o => new GetChannelsVerb(o).Execute());
|
||||||
parsedArgs.WithParsed<GetDirectMessageChannelsOptions>(o => new GetDirectMessageChannelsVerb(o).Execute());
|
parsedArgs.WithParsed<GetDirectMessageChannelsOptions>(o => new GetDirectMessageChannelsVerb(o).Execute());
|
||||||
parsedArgs.WithParsed<GetGuildsOptions>(o => new GetGuildsVerb(o).Execute());
|
parsedArgs.WithParsed<GetGuildsOptions>(o => new GetGuildsVerb(o).Execute());
|
||||||
parsedArgs.WithParsed<UpdateAppOptions>(o => new UpdateAppVerb(o).Execute());
|
|
||||||
|
|
||||||
// Show token help if help requested or no verb specified
|
// Show token help if help requested or no verb specified
|
||||||
parsedArgs.WithNotParsed(errs =>
|
parsedArgs.WithNotParsed(errs =>
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace DiscordChatExporter.Cli.Verbs
|
|||||||
var container = new Container();
|
var container = new Container();
|
||||||
var settingsService = container.Resolve<ISettingsService>();
|
var settingsService = container.Resolve<ISettingsService>();
|
||||||
var dataService = container.Resolve<IDataService>();
|
var dataService = container.Resolve<IDataService>();
|
||||||
var messageGroupService = container.Resolve<IMessageGroupService>();
|
|
||||||
var exportService = container.Resolve<IExportService>();
|
var exportService = container.Resolve<IExportService>();
|
||||||
|
|
||||||
// Configure settings
|
// Configure settings
|
||||||
@@ -30,37 +29,20 @@ namespace DiscordChatExporter.Cli.Verbs
|
|||||||
if (Options.MessageGroupLimit > 0)
|
if (Options.MessageGroupLimit > 0)
|
||||||
settingsService.MessageGroupLimit = Options.MessageGroupLimit;
|
settingsService.MessageGroupLimit = Options.MessageGroupLimit;
|
||||||
|
|
||||||
// Get channel and guild
|
// Get chat log
|
||||||
var channel = await dataService.GetChannelAsync(Options.GetToken(), Options.ChannelId);
|
var chatLog = await dataService.GetChatLogAsync(Options.GetToken(), Options.ChannelId,
|
||||||
var guild = channel.GuildId == Guild.DirectMessages.Id
|
Options.After, Options.Before);
|
||||||
? Guild.DirectMessages
|
|
||||||
: await dataService.GetGuildAsync(Options.GetToken(), channel.GuildId);
|
|
||||||
|
|
||||||
// Generate file path if not set
|
// Generate file path if not set
|
||||||
var filePath = Options.FilePath;
|
var filePath = Options.FilePath;
|
||||||
if (filePath == null || filePath.EndsWith("/") || filePath.EndsWith("\\"))
|
if (filePath == null || filePath.EndsWith("/") || filePath.EndsWith("\\"))
|
||||||
{
|
{
|
||||||
filePath += $"{guild.Name} - {channel.Name}.{Options.ExportFormat.GetFileExtension()}"
|
filePath += $"{chatLog.Guild.Name} - {chatLog.Channel.Name}.{Options.ExportFormat.GetFileExtension()}"
|
||||||
.Replace(Path.GetInvalidFileNameChars(), '_');
|
.Replace(Path.GetInvalidFileNameChars(), '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: extract this to make it reusable across implementations
|
|
||||||
// Get messages
|
|
||||||
var messages =
|
|
||||||
await dataService.GetChannelMessagesAsync(Options.GetToken(), channel.Id,
|
|
||||||
Options.After, Options.Before);
|
|
||||||
|
|
||||||
// Group messages
|
|
||||||
var messageGroups = messageGroupService.GroupMessages(messages);
|
|
||||||
|
|
||||||
// Get mentionables
|
|
||||||
var mentionables = await dataService.GetMentionablesAsync(Options.GetToken(), guild.Id, messages);
|
|
||||||
|
|
||||||
// Create log
|
|
||||||
var log = new ChatLog(guild, channel, Options.After, Options.Before, messageGroups, mentionables);
|
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
exportService.Export(Options.ExportFormat, filePath, log);
|
exportService.ExportChatLog(chatLog, filePath, Options.ExportFormat, Options.PartitionLimit);
|
||||||
|
|
||||||
// Print result
|
// Print result
|
||||||
Console.WriteLine($"Exported chat to [{filePath}]");
|
Console.WriteLine($"Exported chat to [{filePath}]");
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ namespace DiscordChatExporter.Cli.Verbs.Options
|
|||||||
[Option("before", Default = null, HelpText = "Limit to messages sent before this date.")]
|
[Option("before", Default = null, HelpText = "Limit to messages sent before this date.")]
|
||||||
public DateTime? Before { get; set; }
|
public DateTime? Before { get; set; }
|
||||||
|
|
||||||
|
[Option('p', "partition", Default = null, HelpText = "Split output into partitions limited to this number of messages.")]
|
||||||
|
public int? PartitionLimit { get; set; }
|
||||||
|
|
||||||
[Option("dateformat", Default = null, HelpText = "Date format used in output.")]
|
[Option("dateformat", Default = null, HelpText = "Date format used in output.")]
|
||||||
public string DateFormat { get; set; }
|
public string DateFormat { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
using CommandLine;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Verbs.Options
|
|
||||||
{
|
|
||||||
[Verb("update", HelpText = "Updates this application to the latest version.")]
|
|
||||||
public class UpdateAppOptions
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using DiscordChatExporter.Cli.Verbs.Options;
|
|
||||||
using DiscordChatExporter.Core.Services;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.Verbs
|
|
||||||
{
|
|
||||||
public class UpdateAppVerb : Verb<UpdateAppOptions>
|
|
||||||
{
|
|
||||||
public UpdateAppVerb(UpdateAppOptions options)
|
|
||||||
: base(options)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task ExecuteAsync()
|
|
||||||
{
|
|
||||||
// Get update service
|
|
||||||
var container = new Container();
|
|
||||||
var updateService = container.Resolve<IUpdateService>();
|
|
||||||
|
|
||||||
// TODO: this is configured only for GUI
|
|
||||||
// Get update version
|
|
||||||
var updateVersion = await updateService.CheckPrepareUpdateAsync();
|
|
||||||
|
|
||||||
if (updateVersion != null)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Updating to version {updateVersion}");
|
|
||||||
|
|
||||||
updateService.NeedRestart = false;
|
|
||||||
updateService.FinalizeUpdate();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("There are no application updates available.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net461</TargetFramework>
|
<TargetFramework>net461</TargetFramework>
|
||||||
<Version>2.7</Version>
|
<Version>2.8</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Models
|
namespace DiscordChatExporter.Core.Models
|
||||||
{
|
{
|
||||||
@@ -14,20 +13,18 @@ namespace DiscordChatExporter.Core.Models
|
|||||||
|
|
||||||
public DateTime? To { get; }
|
public DateTime? To { get; }
|
||||||
|
|
||||||
public IReadOnlyList<MessageGroup> MessageGroups { get; }
|
public IReadOnlyList<Message> Messages { get; }
|
||||||
|
|
||||||
public int TotalMessageCount => MessageGroups.Sum(g => g.Messages.Count);
|
|
||||||
|
|
||||||
public Mentionables Mentionables { get; }
|
public Mentionables Mentionables { get; }
|
||||||
|
|
||||||
public ChatLog(Guild guild, Channel channel, DateTime? from, DateTime? to,
|
public ChatLog(Guild guild, Channel channel, DateTime? from, DateTime? to,
|
||||||
IReadOnlyList<MessageGroup> messageGroups, Mentionables mentionables)
|
IReadOnlyList<Message> messages, Mentionables mentionables)
|
||||||
{
|
{
|
||||||
Guild = guild;
|
Guild = guild;
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
From = from;
|
From = from;
|
||||||
To = to;
|
To = to;
|
||||||
MessageGroups = messageGroups;
|
Messages = messages;
|
||||||
Mentionables = mentionables;
|
Mentionables = mentionables;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Models
|
namespace DiscordChatExporter.Core.Models
|
||||||
{
|
{
|
||||||
@@ -31,5 +33,45 @@ namespace DiscordChatExporter.Core.Models
|
|||||||
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(format));
|
throw new ArgumentOutOfRangeException(nameof(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<ChatLog> SplitIntoPartitions(this ChatLog chatLog, int partitionLimit)
|
||||||
|
{
|
||||||
|
// If chat log has fewer messages than the limit - just return chat log in a list
|
||||||
|
if (chatLog.Messages.Count <= partitionLimit)
|
||||||
|
return new[] {chatLog};
|
||||||
|
|
||||||
|
var result = new List<ChatLog>();
|
||||||
|
|
||||||
|
// Loop through messages
|
||||||
|
var buffer = new List<Message>();
|
||||||
|
foreach (var message in chatLog.Messages)
|
||||||
|
{
|
||||||
|
// Add message to buffer
|
||||||
|
buffer.Add(message);
|
||||||
|
|
||||||
|
// If reached the limit - split and reset buffer
|
||||||
|
if (buffer.Count >= partitionLimit)
|
||||||
|
{
|
||||||
|
// Add to result
|
||||||
|
var chatLogPartition = new ChatLog(chatLog.Guild, chatLog.Channel, chatLog.From, chatLog.To, buffer,
|
||||||
|
chatLog.Mentionables);
|
||||||
|
result.Add(chatLogPartition);
|
||||||
|
|
||||||
|
// Reset the buffer instead of clearing to avoid mutations on existing references
|
||||||
|
buffer = new List<Message>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add what's remaining in buffer
|
||||||
|
if (buffer.Any())
|
||||||
|
{
|
||||||
|
// Add to result
|
||||||
|
var chatLogPartition = new ChatLog(chatLog.Guild, chatLog.Channel, chatLog.From, chatLog.To, buffer,
|
||||||
|
chatLog.Mentionables);
|
||||||
|
result.Add(chatLogPartition);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Models
|
|
||||||
{
|
|
||||||
public class MessageGroup
|
|
||||||
{
|
|
||||||
public User Author { get; }
|
|
||||||
|
|
||||||
public DateTime Timestamp { get; }
|
|
||||||
|
|
||||||
public IReadOnlyList<Message> Messages { get; }
|
|
||||||
|
|
||||||
public MessageGroup(User author, DateTime timestamp, IReadOnlyList<Message> messages)
|
|
||||||
{
|
|
||||||
Author = author;
|
|
||||||
Timestamp = timestamp;
|
|
||||||
Messages = messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => $"{Author.FullName} | {Timestamp} | {Messages.Count} messages";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
Author;Date;Content;Attachments;
|
Author;Date;Content;Attachments;
|
||||||
{{~ for group in Model.MessageGroups -}}
|
{{~ for message in Model.Messages -}}
|
||||||
{{- for message in group.Messages -}}
|
{{- }}"{{ message.Author.FullName }}";
|
||||||
{{- message.Author.FullName }};
|
|
||||||
|
|
||||||
{{- message.Timestamp | FormatDate }};
|
{{- }}"{{ message.Timestamp | FormatDate }}";
|
||||||
|
|
||||||
{{- message.Content | FormatContent }};
|
{{- }}"{{ message.Content | FormatContent }}";
|
||||||
|
|
||||||
{{- message.Attachments | array.map "Url" | array.join "," }};
|
{{- }}"{{ message.Attachments | array.map "Url" | array.join "," }}";
|
||||||
{{~ end -}}
|
{{~ end -}}
|
||||||
{{- end -}}
|
|
||||||
|
Can't render this file because it contains an unexpected character in line 10 and column 41.
|
@@ -24,7 +24,7 @@
|
|||||||
<div class="info__channel-topic">{{ Model.Channel.Topic | html.escape }}</div>
|
<div class="info__channel-topic">{{ Model.Channel.Topic | html.escape }}</div>
|
||||||
{{~ end ~}}
|
{{~ end ~}}
|
||||||
|
|
||||||
<div class="info__channel-message-count">{{ Model.TotalMessageCount | Format "N0" }} messages</div>
|
<div class="info__channel-message-count">{{ Model.Messages | array.size | Format "N0" }} messages</div>
|
||||||
|
|
||||||
{{~ if Model.From || Model.To ~}}
|
{{~ if Model.From || Model.To ~}}
|
||||||
<div class="info__channel-date-range">
|
<div class="info__channel-date-range">
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
{{~ # Log ~}}
|
{{~ # Log ~}}
|
||||||
<div class="chatlog">
|
<div class="chatlog">
|
||||||
{{~ for group in Model.MessageGroups ~}}
|
{{~ for group in Model.Messages | GroupMessages ~}}
|
||||||
<div class="chatlog__message-group">
|
<div class="chatlog__message-group">
|
||||||
{{~ # Avatar ~}}
|
{{~ # Avatar ~}}
|
||||||
<div class="chatlog__author-avatar-container">
|
<div class="chatlog__author-avatar-container">
|
||||||
|
|||||||
@@ -3,23 +3,19 @@
|
|||||||
Guild: {{ Model.Guild.Name }}
|
Guild: {{ Model.Guild.Name }}
|
||||||
Channel: {{ Model.Channel.Name }}
|
Channel: {{ Model.Channel.Name }}
|
||||||
Topic: {{ Model.Channel.Topic }}
|
Topic: {{ Model.Channel.Topic }}
|
||||||
Messages: {{ Model.TotalMessageCount | Format "N0" }}
|
Messages: {{ Model.Messages | array.size | Format "N0" }}
|
||||||
Range: {{ if Model.From }}{{ Model.From | FormatDate }} {{ end }}{{ if Model.From || Model.To }}->{{ end }}{{ if Model.To }} {{ Model.To | FormatDate }}{{ end }}
|
Range: {{ if Model.From }}{{ Model.From | FormatDate }} {{ end }}{{ if Model.From || Model.To }}->{{ end }}{{ if Model.To }} {{ Model.To | FormatDate }}{{ end }}
|
||||||
==============================================================
|
==============================================================
|
||||||
|
|
||||||
{{~ # Log ~}}
|
{{~ # Log ~}}
|
||||||
{{~ for group in Model.MessageGroups ~}}
|
{{~ for message in Model.Messages ~}}
|
||||||
{{~ # Author name and timestamp ~}}
|
{{~ # Author name and timestamp ~}}
|
||||||
{{~ }}[{{ group.Timestamp | FormatDate }}] {{ group.Author.FullName }}
|
{{~ }}[{{ message.Timestamp | FormatDate }}] {{ message.Author.FullName }}
|
||||||
{{~ # Messages ~}}
|
{{~ # Content ~}}
|
||||||
{{~ for message in group.Messages ~}}
|
{{~ message.Content | FormatContent }}
|
||||||
{{~ # Content ~}}
|
{{~ # Attachments ~}}
|
||||||
{{~ message.Content | FormatContent }}
|
{{~ for attachment in message.Attachments ~}}
|
||||||
{{~ # Attachments ~}}
|
{{~ attachment.Url }}
|
||||||
{{~ for attachment in message.Attachments ~}}
|
|
||||||
{{~ attachment.Url }}
|
|
||||||
{{~ end ~}}
|
|
||||||
{{~ end ~}}
|
{{~ end ~}}
|
||||||
|
|
||||||
|
|
||||||
{{~ end ~}}
|
{{~ end ~}}
|
||||||
@@ -65,6 +65,10 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
|
|
||||||
public async Task<Guild> GetGuildAsync(AuthToken token, string guildId)
|
public async Task<Guild> GetGuildAsync(AuthToken token, string guildId)
|
||||||
{
|
{
|
||||||
|
// Special case for direct messages pseudo-guild
|
||||||
|
if (guildId == Guild.DirectMessages.Id)
|
||||||
|
return Guild.DirectMessages;
|
||||||
|
|
||||||
var response = await GetApiResponseAsync(token, "guilds", guildId);
|
var response = await GetApiResponseAsync(token, "guilds", guildId);
|
||||||
var guild = ParseGuild(response);
|
var guild = ParseGuild(response);
|
||||||
|
|
||||||
@@ -210,6 +214,33 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
return new Mentionables(users, channels, roles);
|
return new Mentionables(users, channels, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Guild guild, Channel channel,
|
||||||
|
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
||||||
|
{
|
||||||
|
// Get messages
|
||||||
|
var messages = await GetChannelMessagesAsync(token, channel.Id, from, to, progress);
|
||||||
|
|
||||||
|
// Get mentionables
|
||||||
|
var mentionables = await GetMentionablesAsync(token, guild.Id, messages);
|
||||||
|
|
||||||
|
return new ChatLog(guild, channel, from, to, messages, mentionables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ChatLog> GetChatLogAsync(AuthToken token, string channelId,
|
||||||
|
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
||||||
|
{
|
||||||
|
// Get channel
|
||||||
|
var channel = await GetChannelAsync(token, channelId);
|
||||||
|
|
||||||
|
// Get guild
|
||||||
|
var guild = channel.GuildId == Guild.DirectMessages.Id
|
||||||
|
? Guild.DirectMessages
|
||||||
|
: await GetGuildAsync(token, channel.GuildId);
|
||||||
|
|
||||||
|
// Get the chat log
|
||||||
|
return await GetChatLogAsync(token, guild, channel, from, to, progress);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_httpClient.Dispose();
|
_httpClient.Dispose();
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using DiscordChatExporter.Core.Models;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Services
|
||||||
|
{
|
||||||
|
public partial class ExportService
|
||||||
|
{
|
||||||
|
private class MessageGroup
|
||||||
|
{
|
||||||
|
public User Author { get; }
|
||||||
|
|
||||||
|
public DateTime Timestamp { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<Message> Messages { get; }
|
||||||
|
|
||||||
|
public MessageGroup(User author, DateTime timestamp, IReadOnlyList<Message> messages)
|
||||||
|
{
|
||||||
|
Author = author;
|
||||||
|
Timestamp = timestamp;
|
||||||
|
Messages = messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -18,12 +19,57 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
private readonly ExportFormat _format;
|
private readonly ExportFormat _format;
|
||||||
private readonly ChatLog _log;
|
private readonly ChatLog _log;
|
||||||
private readonly string _dateFormat;
|
private readonly string _dateFormat;
|
||||||
|
private readonly int _messageGroupLimit;
|
||||||
|
|
||||||
public TemplateModel(ExportFormat format, ChatLog log, string dateFormat)
|
public TemplateModel(ExportFormat format, ChatLog log, string dateFormat, int messageGroupLimit)
|
||||||
{
|
{
|
||||||
_format = format;
|
_format = format;
|
||||||
_log = log;
|
_log = log;
|
||||||
_dateFormat = dateFormat;
|
_dateFormat = dateFormat;
|
||||||
|
_messageGroupLimit = messageGroupLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<MessageGroup> GroupMessages(IEnumerable<Message> messages)
|
||||||
|
{
|
||||||
|
// Group adjacent messages by timestamp and author
|
||||||
|
var buffer = new List<Message>();
|
||||||
|
foreach (var message in messages)
|
||||||
|
{
|
||||||
|
var groupFirst = buffer.FirstOrDefault();
|
||||||
|
|
||||||
|
// Group break condition
|
||||||
|
var breakCondition =
|
||||||
|
groupFirst != null &&
|
||||||
|
(
|
||||||
|
message.Author.Id != groupFirst.Author.Id ||
|
||||||
|
(message.Timestamp - groupFirst.Timestamp).TotalHours > 1 ||
|
||||||
|
message.Timestamp.Hour != groupFirst.Timestamp.Hour ||
|
||||||
|
buffer.Count >= _messageGroupLimit
|
||||||
|
);
|
||||||
|
|
||||||
|
// If condition is true - flush buffer
|
||||||
|
if (breakCondition)
|
||||||
|
{
|
||||||
|
var group = new MessageGroup(groupFirst.Author, groupFirst.Timestamp, buffer);
|
||||||
|
|
||||||
|
// Reset the buffer instead of clearing to avoid mutations on existing references
|
||||||
|
buffer = new List<Message>();
|
||||||
|
|
||||||
|
yield return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add message to buffer
|
||||||
|
buffer.Add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add what's remaining in buffer
|
||||||
|
if (buffer.Any())
|
||||||
|
{
|
||||||
|
var groupFirst = buffer.First();
|
||||||
|
var group = new MessageGroup(groupFirst.Author, groupFirst.Timestamp, buffer);
|
||||||
|
|
||||||
|
yield return group;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string HtmlEncode(string str) => WebUtility.HtmlEncode(str);
|
private string HtmlEncode(string str) => WebUtility.HtmlEncode(str);
|
||||||
@@ -229,9 +275,6 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
|
|
||||||
private string FormatContentCsv(string content)
|
private string FormatContentCsv(string content)
|
||||||
{
|
{
|
||||||
// New lines
|
|
||||||
content = content.Replace("\n", ", ");
|
|
||||||
|
|
||||||
// Escape quotes
|
// Escape quotes
|
||||||
content = content.Replace("\"", "\"\"");
|
content = content.Replace("\"", "\"\"");
|
||||||
|
|
||||||
@@ -310,6 +353,7 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
scriptObject.SetValue("Model", _log, true);
|
scriptObject.SetValue("Model", _log, true);
|
||||||
|
|
||||||
// Import functions
|
// Import functions
|
||||||
|
scriptObject.Import(nameof(GroupMessages), new Func<IEnumerable<Message>, IEnumerable<MessageGroup>>(GroupMessages));
|
||||||
scriptObject.Import(nameof(Format), new Func<IFormattable, string, string>(Format));
|
scriptObject.Import(nameof(Format), new Func<IFormattable, string, string>(Format));
|
||||||
scriptObject.Import(nameof(FormatDate), new Func<DateTime, string>(FormatDate));
|
scriptObject.Import(nameof(FormatDate), new Func<DateTime, string>(FormatDate));
|
||||||
scriptObject.Import(nameof(FormatFileSize), new Func<long, string>(FormatFileSize));
|
scriptObject.Import(nameof(FormatFileSize), new Func<long, string>(FormatFileSize));
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.IO;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using DiscordChatExporter.Core.Models;
|
using DiscordChatExporter.Core.Models;
|
||||||
using Scriban;
|
using Scriban;
|
||||||
using Scriban.Runtime;
|
using Scriban.Runtime;
|
||||||
@@ -15,7 +16,7 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Export(ExportFormat format, string filePath, ChatLog log)
|
private void ExportChatLogSingle(ChatLog chatLog, string filePath, ExportFormat format)
|
||||||
{
|
{
|
||||||
// Create template loader
|
// Create template loader
|
||||||
var loader = new TemplateLoader();
|
var loader = new TemplateLoader();
|
||||||
@@ -35,7 +36,9 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Create template model
|
// Create template model
|
||||||
var templateModel = new TemplateModel(format, log, _settingsService.DateFormat);
|
var templateModel = new TemplateModel(format, chatLog,
|
||||||
|
_settingsService.DateFormat, _settingsService.MessageGroupLimit);
|
||||||
|
|
||||||
context.PushGlobal(templateModel.GetScriptObject());
|
context.PushGlobal(templateModel.GetScriptObject());
|
||||||
|
|
||||||
// Create directory
|
// Create directory
|
||||||
@@ -49,8 +52,50 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
// Configure output
|
// Configure output
|
||||||
context.PushOutput(new TextWriterOutput(output));
|
context.PushOutput(new TextWriterOutput(output));
|
||||||
|
|
||||||
// Render template
|
// Render output
|
||||||
template.Render(context);
|
context.Evaluate(template.Page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportChatLogPartitions(IReadOnlyList<ChatLog> partitions, string filePath, ExportFormat format)
|
||||||
|
{
|
||||||
|
// Split file path into components
|
||||||
|
var dirPath = Path.GetDirectoryName(filePath);
|
||||||
|
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(filePath);
|
||||||
|
var fileExt = Path.GetExtension(filePath);
|
||||||
|
|
||||||
|
// Export each partition separately
|
||||||
|
var partitionNumber = 1;
|
||||||
|
foreach (var partition in partitions)
|
||||||
|
{
|
||||||
|
// Compose new file name
|
||||||
|
var partitionFilePath = $"{fileNameWithoutExt}-{partitionNumber}{fileExt}";
|
||||||
|
|
||||||
|
// Compose full file path
|
||||||
|
if (dirPath.IsNotBlank())
|
||||||
|
partitionFilePath = Path.Combine(dirPath, partitionFilePath);
|
||||||
|
|
||||||
|
// Export
|
||||||
|
ExportChatLogSingle(partition, partitionFilePath, format);
|
||||||
|
|
||||||
|
// Increment partition number
|
||||||
|
partitionNumber++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExportChatLog(ChatLog chatLog, string filePath, ExportFormat format,
|
||||||
|
int? partitionLimit = null)
|
||||||
|
{
|
||||||
|
// If partitioning is disabled or there are fewer messages in chat log than the limit - process it without partitioning
|
||||||
|
if (partitionLimit == null || chatLog.Messages.Count <= partitionLimit)
|
||||||
|
{
|
||||||
|
ExportChatLogSingle(chatLog, filePath, format);
|
||||||
|
}
|
||||||
|
// Otherwise split into partitions and export separately
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var partitions = chatLog.SplitIntoPartitions(partitionLimit.Value);
|
||||||
|
ExportChatLogPartitions(partitions, filePath, format);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,5 +24,11 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
|
|
||||||
Task<Mentionables> GetMentionablesAsync(AuthToken token, string guildId,
|
Task<Mentionables> GetMentionablesAsync(AuthToken token, string guildId,
|
||||||
IEnumerable<Message> messages);
|
IEnumerable<Message> messages);
|
||||||
|
|
||||||
|
Task<ChatLog> GetChatLogAsync(AuthToken token, Guild guild, Channel channel,
|
||||||
|
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null);
|
||||||
|
|
||||||
|
Task<ChatLog> GetChatLogAsync(AuthToken token, string channelId,
|
||||||
|
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
{
|
{
|
||||||
public interface IExportService
|
public interface IExportService
|
||||||
{
|
{
|
||||||
void Export(ExportFormat format, string filePath, ChatLog log);
|
void ExportChatLog(ChatLog chatLog, string filePath, ExportFormat format,
|
||||||
|
int? partitionLimit = null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using DiscordChatExporter.Core.Models;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Services
|
|
||||||
{
|
|
||||||
public interface IMessageGroupService
|
|
||||||
{
|
|
||||||
IReadOnlyList<MessageGroup> GroupMessages(IEnumerable<Message> messages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
|
|
||||||
AuthToken LastToken { get; set; }
|
AuthToken LastToken { get; set; }
|
||||||
ExportFormat LastExportFormat { get; set; }
|
ExportFormat LastExportFormat { get; set; }
|
||||||
|
int? LastPartitionLimit { get; set; }
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
void Save();
|
void Save();
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using DiscordChatExporter.Core.Models;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Services
|
|
||||||
{
|
|
||||||
public class MessageGroupService : IMessageGroupService
|
|
||||||
{
|
|
||||||
private readonly ISettingsService _settingsService;
|
|
||||||
|
|
||||||
public MessageGroupService(ISettingsService settingsService)
|
|
||||||
{
|
|
||||||
_settingsService = settingsService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IReadOnlyList<MessageGroup> GroupMessages(IEnumerable<Message> messages)
|
|
||||||
{
|
|
||||||
var result = new List<MessageGroup>();
|
|
||||||
|
|
||||||
// Group adjacent messages by timestamp and author
|
|
||||||
var groupBuffer = new List<Message>();
|
|
||||||
foreach (var message in messages)
|
|
||||||
{
|
|
||||||
var groupFirst = groupBuffer.FirstOrDefault();
|
|
||||||
|
|
||||||
// Group break condition
|
|
||||||
var breakCondition =
|
|
||||||
groupFirst != null &&
|
|
||||||
(
|
|
||||||
message.Author.Id != groupFirst.Author.Id ||
|
|
||||||
(message.Timestamp - groupFirst.Timestamp).TotalHours > 1 ||
|
|
||||||
message.Timestamp.Hour != groupFirst.Timestamp.Hour ||
|
|
||||||
groupBuffer.Count >= _settingsService.MessageGroupLimit
|
|
||||||
);
|
|
||||||
|
|
||||||
// If condition is true - flush buffer
|
|
||||||
if (breakCondition)
|
|
||||||
{
|
|
||||||
var group = new MessageGroup(groupFirst.Author, groupFirst.Timestamp, groupBuffer.ToArray());
|
|
||||||
result.Add(group);
|
|
||||||
groupBuffer.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add message to buffer
|
|
||||||
groupBuffer.Add(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add what's remaining in buffer
|
|
||||||
if (groupBuffer.Any())
|
|
||||||
{
|
|
||||||
var groupFirst = groupBuffer.First();
|
|
||||||
var group = new MessageGroup(groupFirst.Author, groupFirst.Timestamp, groupBuffer.ToArray());
|
|
||||||
result.Add(group);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@ namespace DiscordChatExporter.Core.Services
|
|||||||
|
|
||||||
public AuthToken LastToken { get; set; }
|
public AuthToken LastToken { get; set; }
|
||||||
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
public int? LastPartitionLimit { get; set; }
|
||||||
|
|
||||||
public SettingsService()
|
public SettingsService()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
|
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
|
||||||
xmlns:local="clr-namespace:DiscordChatExporter.Gui"
|
xmlns:local="clr-namespace:DiscordChatExporter.Gui"
|
||||||
|
DispatcherUnhandledException="App_OnDispatcherUnhandledException"
|
||||||
StartupUri="Views/MainWindow.xaml">
|
StartupUri="Views/MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
namespace DiscordChatExporter.Gui
|
using System.Windows;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Gui
|
||||||
{
|
{
|
||||||
public partial class App
|
public partial class App
|
||||||
{
|
{
|
||||||
|
private void App_OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs args)
|
||||||
|
{
|
||||||
|
MessageBox.Show(args.Exception.ToString(), "Error occured", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,7 +19,6 @@ namespace DiscordChatExporter.Gui
|
|||||||
// 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<ISettingsService, SettingsService>();
|
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
||||||
SimpleIoc.Default.Register<IUpdateService, UpdateService>();
|
SimpleIoc.Default.Register<IUpdateService, UpdateService>();
|
||||||
|
|
||||||
|
|||||||
@@ -42,39 +42,12 @@
|
|||||||
<StartupObject />
|
<StartupObject />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="CommonServiceLocator, Version=2.0.3.0, Culture=neutral, PublicKeyToken=489b6accfaf20ef0, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\CommonServiceLocator.2.0.3\lib\net45\CommonServiceLocator.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="GalaSoft.MvvmLight, Version=5.4.1.0, Culture=neutral, PublicKeyToken=e7570ab207bcb616, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.4.1\lib\net45\GalaSoft.MvvmLight.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="GalaSoft.MvvmLight.Extras, Version=5.4.1.0, Culture=neutral, PublicKeyToken=669f0b5e8f868abf, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.4.1\lib\net45\GalaSoft.MvvmLight.Extras.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="GalaSoft.MvvmLight.Platform, Version=5.4.1.0, Culture=neutral, PublicKeyToken=5f873c45e98af8a1, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.4.1\lib\net45\GalaSoft.MvvmLight.Platform.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="MaterialDesignColors, Version=1.1.3.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MaterialDesignColors.1.1.3\lib\net45\MaterialDesignColors.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="MaterialDesignThemes.Wpf, Version=2.4.0.1044, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MaterialDesignThemes.2.4.0.1044\lib\net45\MaterialDesignThemes.Wpf.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Windows.Interactivity, Version=4.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\MvvmLightLibs.5.4.1\lib\net45\System.Windows.Interactivity.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="System.Xaml">
|
<Reference Include="System.Xaml">
|
||||||
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
<RequiredTargetFramework>4.0</RequiredTargetFramework>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="Tyrrrz.Extensions, Version=1.5.1.0, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Tyrrrz.Extensions.1.5.1\lib\net45\Tyrrrz.Extensions.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="Tyrrrz.Settings, Version=1.3.2.0, Culture=neutral, PublicKeyToken=null" />
|
<Reference Include="Tyrrrz.Settings, Version=1.3.2.0, Culture=neutral, PublicKeyToken=null" />
|
||||||
<Reference Include="Tyrrrz.WpfExtensions, Version=1.0.6269.37170, Culture=neutral, processorArchitecture=MSIL">
|
|
||||||
<HintPath>..\packages\Tyrrrz.WpfExtensions.1.0.5\lib\net45\Tyrrrz.WpfExtensions.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
<Reference Include="WindowsBase" />
|
<Reference Include="WindowsBase" />
|
||||||
<Reference Include="PresentationCore" />
|
<Reference Include="PresentationCore" />
|
||||||
<Reference Include="PresentationFramework" />
|
<Reference Include="PresentationFramework" />
|
||||||
@@ -119,9 +92,6 @@
|
|||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
<None Include="packages.config">
|
|
||||||
<SubType>Designer</SubType>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Resource Include="..\favicon.ico" />
|
<Resource Include="..\favicon.ico" />
|
||||||
@@ -150,6 +120,25 @@
|
|||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
</Page>
|
</Page>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommonServiceLocator">
|
||||||
|
<Version>2.0.3</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="MaterialDesignColors">
|
||||||
|
<Version>1.1.3</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="MaterialDesignThemes">
|
||||||
|
<Version>2.4.0.1044</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="MvvmLightLibs">
|
||||||
|
<Version>5.4.1</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Tyrrrz.Extensions">
|
||||||
|
<Version>1.5.1</Version>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Tyrrrz.WpfExtensions">
|
||||||
|
<Version>1.0.5</Version>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
</Project>
|
</Project>
|
||||||
@@ -15,14 +15,17 @@ namespace DiscordChatExporter.Gui.Messages
|
|||||||
|
|
||||||
public DateTime? To { get; }
|
public DateTime? To { get; }
|
||||||
|
|
||||||
|
public int? PartitionLimit { get; }
|
||||||
|
|
||||||
public StartExportMessage(Channel channel, string filePath, ExportFormat format,
|
public StartExportMessage(Channel channel, string filePath, ExportFormat format,
|
||||||
DateTime? from, DateTime? to)
|
DateTime? from, DateTime? to, int? partitionLimit)
|
||||||
{
|
{
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
FilePath = filePath;
|
FilePath = filePath;
|
||||||
Format = format;
|
Format = format;
|
||||||
From = from;
|
From = from;
|
||||||
To = to;
|
To = to;
|
||||||
|
PartitionLimit = partitionLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
[assembly: AssemblyTitle("DiscordChatExporter")]
|
[assembly: AssemblyTitle("DiscordChatExporter")]
|
||||||
[assembly: AssemblyCompany("Tyrrrz")]
|
[assembly: AssemblyCompany("Tyrrrz")]
|
||||||
[assembly: AssemblyCopyright("Copyright (c) 2017-2018 Alexey Golub")]
|
[assembly: AssemblyCopyright("Copyright (c) 2017-2018 Alexey Golub")]
|
||||||
[assembly: AssemblyVersion("2.7")]
|
[assembly: AssemblyVersion("2.8")]
|
||||||
[assembly: AssemblyFileVersion("2.7")]
|
[assembly: AssemblyFileVersion("2.8")]
|
||||||
@@ -19,6 +19,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
private ExportFormat _format;
|
private ExportFormat _format;
|
||||||
private DateTime? _from;
|
private DateTime? _from;
|
||||||
private DateTime? _to;
|
private DateTime? _to;
|
||||||
|
private int? _partitionLimit;
|
||||||
|
|
||||||
public Guild Guild { get; private set; }
|
public Guild Guild { get; private set; }
|
||||||
|
|
||||||
@@ -63,6 +64,12 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
set => Set(ref _to, value);
|
set => Set(ref _to, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int? PartitionLimit
|
||||||
|
{
|
||||||
|
get => _partitionLimit;
|
||||||
|
set => Set(ref _partitionLimit, value);
|
||||||
|
}
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
public RelayCommand ExportCommand { get; }
|
public RelayCommand ExportCommand { get; }
|
||||||
|
|
||||||
@@ -83,13 +90,15 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
.Replace(Path.GetInvalidFileNameChars(), '_');
|
.Replace(Path.GetInvalidFileNameChars(), '_');
|
||||||
From = null;
|
From = null;
|
||||||
To = null;
|
To = null;
|
||||||
|
PartitionLimit = _settingsService.LastPartitionLimit;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Export()
|
private void Export()
|
||||||
{
|
{
|
||||||
// Save format
|
// Persist preferences
|
||||||
_settingsService.LastExportFormat = SelectedFormat;
|
_settingsService.LastExportFormat = SelectedFormat;
|
||||||
|
_settingsService.LastPartitionLimit = PartitionLimit;
|
||||||
|
|
||||||
// Clamp 'from' and 'to' values
|
// Clamp 'from' and 'to' values
|
||||||
if (From > To)
|
if (From > To)
|
||||||
@@ -98,7 +107,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
To = From;
|
To = From;
|
||||||
|
|
||||||
// Start export
|
// Start export
|
||||||
MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat, From, To));
|
MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat, From, To, PartitionLimit));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,6 +14,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
ExportFormat SelectedFormat { get; set; }
|
ExportFormat SelectedFormat { get; set; }
|
||||||
DateTime? From { get; set; }
|
DateTime? From { get; set; }
|
||||||
DateTime? To { get; set; }
|
DateTime? To { get; set; }
|
||||||
|
int? PartitionLimit { get; set; }
|
||||||
|
|
||||||
RelayCommand ExportCommand { get; }
|
RelayCommand ExportCommand { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly IUpdateService _updateService;
|
private readonly IUpdateService _updateService;
|
||||||
private readonly IDataService _dataService;
|
private readonly IDataService _dataService;
|
||||||
private readonly IMessageGroupService _messageGroupService;
|
|
||||||
private readonly IExportService _exportService;
|
private readonly IExportService _exportService;
|
||||||
|
|
||||||
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
|
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
|
||||||
@@ -111,12 +110,11 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
public RelayCommand<Channel> ShowExportSetupCommand { get; }
|
public RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||||
|
|
||||||
public MainViewModel(ISettingsService settingsService, IUpdateService updateService, IDataService dataService,
|
public MainViewModel(ISettingsService settingsService, IUpdateService updateService, IDataService dataService,
|
||||||
IMessageGroupService messageGroupService, IExportService exportService)
|
IExportService exportService)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_updateService = updateService;
|
_updateService = updateService;
|
||||||
_dataService = dataService;
|
_dataService = dataService;
|
||||||
_messageGroupService = messageGroupService;
|
|
||||||
_exportService = exportService;
|
_exportService = exportService;
|
||||||
|
|
||||||
_guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
|
_guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
|
||||||
@@ -131,7 +129,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
MessengerInstance.Register<StartExportMessage>(this,
|
MessengerInstance.Register<StartExportMessage>(this,
|
||||||
m => Export(m.Channel, m.FilePath, m.Format, m.From, m.To));
|
m => Export(m.Channel, m.FilePath, m.Format, m.From, m.To, m.PartitionLimit));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ViewLoaded()
|
private async void ViewLoaded()
|
||||||
@@ -241,7 +239,8 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
MessengerInstance.Send(new ShowExportSetupMessage(SelectedGuild, channel));
|
MessengerInstance.Send(new ShowExportSetupMessage(SelectedGuild, channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Export(Channel channel, string filePath, ExportFormat format, DateTime? from, DateTime? to)
|
private async void Export(Channel channel, string filePath, ExportFormat format,
|
||||||
|
DateTime? from, DateTime? to, int? partitionLimit)
|
||||||
{
|
{
|
||||||
IsBusy = true;
|
IsBusy = true;
|
||||||
|
|
||||||
@@ -256,24 +255,11 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// TODO: extract this to make it reusable across implementations
|
// Get chat log
|
||||||
// Get messages
|
var chatLog = await _dataService.GetChatLogAsync(token, guild, channel, from, to, progressHandler);
|
||||||
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to, progressHandler);
|
|
||||||
|
|
||||||
// Group messages
|
|
||||||
var messageGroups = _messageGroupService.GroupMessages(messages);
|
|
||||||
|
|
||||||
// Get mentionables
|
|
||||||
var mentionables = await _dataService.GetMentionablesAsync(token, guild.Id, messages);
|
|
||||||
|
|
||||||
// Create log
|
|
||||||
var log = new ChatLog(guild, channel, from, to, messageGroups, mentionables);
|
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
_exportService.Export(format, filePath, log);
|
_exportService.ExportChatLog(chatLog, filePath, format, partitionLimit);
|
||||||
|
|
||||||
// Open
|
|
||||||
Process.Start(filePath);
|
|
||||||
|
|
||||||
// Notify completion
|
// Notify completion
|
||||||
MessengerInstance.Send(new ShowNotificationMessage("Export complete"));
|
MessengerInstance.Send(new ShowNotificationMessage("Export complete"));
|
||||||
|
|||||||
@@ -55,6 +55,13 @@
|
|||||||
SelectedDate="{Binding To}" />
|
SelectedDate="{Binding To}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Partitioning -->
|
||||||
|
<TextBox
|
||||||
|
Margin="16,8,16,8"
|
||||||
|
materialDesign:HintAssist.Hint="Messages per partition (optional)"
|
||||||
|
materialDesign:HintAssist.IsFloating="True"
|
||||||
|
Text="{Binding PartitionLimit, TargetNullValue=''}" />
|
||||||
|
|
||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<packages>
|
|
||||||
<package id="CommonServiceLocator" version="2.0.3" targetFramework="net461" />
|
|
||||||
<package id="MaterialDesignColors" version="1.1.3" targetFramework="net461" />
|
|
||||||
<package id="MaterialDesignThemes" version="2.4.0.1044" targetFramework="net461" />
|
|
||||||
<package id="MvvmLightLibs" version="5.4.1" targetFramework="net461" />
|
|
||||||
<package id="Tyrrrz.Extensions" version="1.5.1" targetFramework="net461" />
|
|
||||||
<package id="Tyrrrz.WpfExtensions" version="1.0.5" targetFramework="net461" />
|
|
||||||
</packages>
|
|
||||||
Reference in New Issue
Block a user