Refactor message grouping from data layer to render layer

pull/116/head
Oleksii Holub 6 years ago
parent 23116b776b
commit 47f0561c71

@ -12,10 +12,8 @@ namespace DiscordChatExporter.Cli
SimpleIoc.Default.Reset(); SimpleIoc.Default.Reset();
// Services // Services
SimpleIoc.Default.Register<IChatLogService, ChatLogService>();
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>();
} }

@ -20,7 +20,7 @@ namespace DiscordChatExporter.Cli.Verbs
// Get services // Get services
var container = new Container(); var container = new Container();
var settingsService = container.Resolve<ISettingsService>(); var settingsService = container.Resolve<ISettingsService>();
var chatLogService = container.Resolve<IChatLogService>(); var dataService = container.Resolve<IDataService>();
var exportService = container.Resolve<IExportService>(); var exportService = container.Resolve<IExportService>();
// Configure settings // Configure settings
@ -30,7 +30,7 @@ namespace DiscordChatExporter.Cli.Verbs
settingsService.MessageGroupLimit = Options.MessageGroupLimit; settingsService.MessageGroupLimit = Options.MessageGroupLimit;
// Get chat log // Get chat log
var chatLog = await chatLogService.GetChatLogAsync(Options.GetToken(), Options.ChannelId, var chatLog = await dataService.GetChatLogAsync(Options.GetToken(), Options.ChannelId,
Options.After, Options.Before); Options.After, Options.Before);
// Generate file path if not set // Generate file path if not set

@ -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,21 +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 long TotalMessageCount { get; }
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, long totalMessageCount, 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;
TotalMessageCount = totalMessageCount;
Mentionables = mentionables; Mentionables = mentionables;
} }

@ -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,12 +3,12 @@
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 group in Model.Messages | GroupMessages ~}}
{{~ # Author name and timestamp ~}} {{~ # Author name and timestamp ~}}
{{~ }}[{{ group.Timestamp | FormatDate }}] {{ group.Author.FullName }} {{~ }}[{{ group.Timestamp | FormatDate }}] {{ group.Author.FullName }}
{{~ # Messages ~}} {{~ # Messages ~}}

@ -1,51 +0,0 @@
using System;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Models;
namespace DiscordChatExporter.Core.Services
{
public class ChatLogService : IChatLogService
{
private readonly IDataService _dataService;
private readonly IMessageGroupService _messageGroupService;
public ChatLogService(IDataService dataService, IMessageGroupService messageGroupService)
{
_dataService = dataService;
_messageGroupService = messageGroupService;
}
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 _dataService.GetChannelMessagesAsync(token, channel.Id, from, to, progress);
// Group messages
var messageGroups = _messageGroupService.GroupMessages(messages);
// Get total message count
var totalMessageCount = messages.Count;
// Get mentionables
var mentionables = await _dataService.GetMentionablesAsync(token, guild.Id, messages);
return new ChatLog(guild, channel, from, to, messageGroups, totalMessageCount, 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 _dataService.GetChannelAsync(token, channelId);
// Get guild
var guild = channel.GuildId == Guild.DirectMessages.Id
? Guild.DirectMessages
: await _dataService.GetGuildAsync(token, channel.GuildId);
// Get the chat log
return await GetChatLogAsync(token, guild, channel, from, to, progress);
}
}
}

@ -214,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,55 @@ 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 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 >= _messageGroupLimit
);
// If condition is true - flush buffer
if (breakCondition)
{
var group = new MessageGroup(groupFirst.Author, groupFirst.Timestamp, groupBuffer.ToArray());
groupBuffer.Clear();
yield return group;
}
// 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());
yield return group;
}
} }
private string HtmlEncode(string str) => WebUtility.HtmlEncode(str); private string HtmlEncode(string str) => WebUtility.HtmlEncode(str);
@ -310,6 +354,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));

@ -35,7 +35,9 @@ namespace DiscordChatExporter.Core.Services
}; };
// Create template model // Create template model
var templateModel = new TemplateModel(format, chatLog, _settingsService.DateFormat); var templateModel = new TemplateModel(format, chatLog,
_settingsService.DateFormat, _settingsService.MessageGroupLimit);
context.PushGlobal(templateModel.GetScriptObject()); context.PushGlobal(templateModel.GetScriptObject());
// Create directory // Create directory

@ -1,15 +0,0 @@
using System;
using System.Threading.Tasks;
using DiscordChatExporter.Core.Models;
namespace DiscordChatExporter.Core.Services
{
public interface IChatLogService
{
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);
}
}

@ -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);
} }
} }

@ -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);
}
}

@ -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;
}
}
}

@ -17,10 +17,8 @@ namespace DiscordChatExporter.Gui
SimpleIoc.Default.Reset(); SimpleIoc.Default.Reset();
// Services // Services
SimpleIoc.Default.Register<IChatLogService, ChatLogService>();
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>();

@ -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 IChatLogService _chatLogService;
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,
IChatLogService chatLogService, IExportService exportService) IExportService exportService)
{ {
_settingsService = settingsService; _settingsService = settingsService;
_updateService = updateService; _updateService = updateService;
_dataService = dataService; _dataService = dataService;
_chatLogService = chatLogService;
_exportService = exportService; _exportService = exportService;
_guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>(); _guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
@ -257,7 +255,7 @@ namespace DiscordChatExporter.Gui.ViewModels
try try
{ {
// Get chat log // Get chat log
var chatLog = await _chatLogService.GetChatLogAsync(token, guild, channel, from, to, progressHandler); var chatLog = await _dataService.GetChatLogAsync(token, guild, channel, from, to, progressHandler);
// Export // Export
_exportService.ExportChatLog(chatLog, filePath, format); _exportService.ExportChatLog(chatLog, filePath, format);

Loading…
Cancel
Save