mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-14 07:43:31 -07:00
Refactor message grouping from data layer to render layer
This commit is contained in:
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,12 +1,10 @@
|
|||
Author;Date;Content;Attachments;
|
||||
{{~ for group in Model.MessageGroups -}}
|
||||
{{- for message in group.Messages -}}
|
||||
{{- message.Author.FullName }};
|
||||
{{~ for message in Model.Messages -}}
|
||||
{{- message.Author.FullName }};
|
||||
|
||||
{{- message.Timestamp | FormatDate }};
|
||||
{{- message.Timestamp | FormatDate }};
|
||||
|
||||
{{- message.Content | FormatContent }};
|
||||
{{- message.Content | FormatContent }};
|
||||
|
||||
{{- message.Attachments | array.map "Url" | array.join "," }};
|
||||
{{~ end -}}
|
||||
{{- end -}}
|
||||
{{- message.Attachments | array.map "Url" | array.join "," }};
|
||||
{{~ 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…
Reference in a new issue