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();
// Services
SimpleIoc.Default.Register<IChatLogService, ChatLogService>();
SimpleIoc.Default.Register<IDataService, DataService>();
SimpleIoc.Default.Register<IExportService, ExportService>();
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
SimpleIoc.Default.Register<IUpdateService, UpdateService>();
}

@ -20,7 +20,7 @@ namespace DiscordChatExporter.Cli.Verbs
// Get services
var container = new Container();
var settingsService = container.Resolve<ISettingsService>();
var chatLogService = container.Resolve<IChatLogService>();
var dataService = container.Resolve<IDataService>();
var exportService = container.Resolve<IExportService>();
// Configure settings
@ -30,7 +30,7 @@ namespace DiscordChatExporter.Cli.Verbs
settingsService.MessageGroupLimit = Options.MessageGroupLimit;
// 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);
// Generate file path if not set

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace DiscordChatExporter.Core.Models
{
@ -14,21 +13,18 @@ namespace DiscordChatExporter.Core.Models
public DateTime? To { get; }
public IReadOnlyList<MessageGroup> MessageGroups { get; }
public long TotalMessageCount { get; }
public IReadOnlyList<Message> Messages { get; }
public Mentionables Mentionables { get; }
public ChatLog(Guild guild, Channel channel, DateTime? from, DateTime? to,
IReadOnlyList<MessageGroup> messageGroups, long totalMessageCount, Mentionables mentionables)
IReadOnlyList<Message> messages, Mentionables mentionables)
{
Guild = guild;
Channel = channel;
From = from;
To = to;
MessageGroups = messageGroups;
TotalMessageCount = totalMessageCount;
Messages = messages;
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,6 +1,5 @@
Author;Date;Content;Attachments;
{{~ for group in Model.MessageGroups -}}
{{- for message in group.Messages -}}
{{~ for message in Model.Messages -}}
{{- message.Author.FullName }};
{{- message.Timestamp | FormatDate }};
@ -9,4 +8,3 @@
{{- message.Attachments | array.map "Url" | array.join "," }};
{{~ 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>
{{~ 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 ~}}
<div class="info__channel-date-range">
@ -42,7 +42,7 @@
{{~ # Log ~}}
<div class="chatlog">
{{~ for group in Model.MessageGroups ~}}
{{~ for group in Model.Messages | GroupMessages ~}}
<div class="chatlog__message-group">
{{~ # Avatar ~}}
<div class="chatlog__author-avatar-container">

@ -3,12 +3,12 @@
Guild: {{ Model.Guild.Name }}
Channel: {{ Model.Channel.Name }}
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 }}
==============================================================
{{~ # Log ~}}
{{~ for group in Model.MessageGroups ~}}
{{~ for group in Model.Messages | GroupMessages ~}}
{{~ # Author name and timestamp ~}}
{{~ }}[{{ group.Timestamp | FormatDate }}] {{ group.Author.FullName }}
{{~ # 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);
}
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()
{
_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.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Linq;
@ -18,12 +19,55 @@ namespace DiscordChatExporter.Core.Services
private readonly ExportFormat _format;
private readonly ChatLog _log;
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;
_log = log;
_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);
@ -310,6 +354,7 @@ namespace DiscordChatExporter.Core.Services
scriptObject.SetValue("Model", _log, true);
// 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(FormatDate), new Func<DateTime, string>(FormatDate));
scriptObject.Import(nameof(FormatFileSize), new Func<long, string>(FormatFileSize));

@ -35,7 +35,9 @@ namespace DiscordChatExporter.Core.Services
};
// 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());
// 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,
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();
// Services
SimpleIoc.Default.Register<IChatLogService, ChatLogService>();
SimpleIoc.Default.Register<IDataService, DataService>();
SimpleIoc.Default.Register<IExportService, ExportService>();
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
SimpleIoc.Default.Register<IUpdateService, UpdateService>();

@ -19,7 +19,6 @@ namespace DiscordChatExporter.Gui.ViewModels
private readonly ISettingsService _settingsService;
private readonly IUpdateService _updateService;
private readonly IDataService _dataService;
private readonly IChatLogService _chatLogService;
private readonly IExportService _exportService;
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
@ -111,12 +110,11 @@ namespace DiscordChatExporter.Gui.ViewModels
public RelayCommand<Channel> ShowExportSetupCommand { get; }
public MainViewModel(ISettingsService settingsService, IUpdateService updateService, IDataService dataService,
IChatLogService chatLogService, IExportService exportService)
IExportService exportService)
{
_settingsService = settingsService;
_updateService = updateService;
_dataService = dataService;
_chatLogService = chatLogService;
_exportService = exportService;
_guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
@ -257,7 +255,7 @@ namespace DiscordChatExporter.Gui.ViewModels
try
{
// 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
_exportService.ExportChatLog(chatLog, filePath, format);

Loading…
Cancel
Save