mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-21 18:35:15 -04:00
parent
2a81abb1a6
commit
290729158b
5 changed files with 67 additions and 50 deletions
|
@ -251,16 +251,17 @@ public class DiscordClient
|
||||||
yield return Role.Parse(roleJson);
|
yield return Role.Parse(roleJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<Member> GetGuildMemberAsync(
|
public async ValueTask<Member?> TryGetGuildMemberAsync(
|
||||||
Snowflake guildId,
|
Snowflake guildId,
|
||||||
User user,
|
Snowflake memberId,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (guildId == Guild.DirectMessages.Id)
|
if (guildId == Guild.DirectMessages.Id)
|
||||||
return Member.CreateForUser(user);
|
return null;
|
||||||
|
|
||||||
var response = await TryGetJsonResponseAsync($"guilds/{guildId}/members/{user.Id}", cancellationToken);
|
var response = await TryGetJsonResponseAsync($"guilds/{guildId}/members/{memberId}", cancellationToken);
|
||||||
return response?.Pipe(Member.Parse) ?? Member.CreateForUser(user);
|
|
||||||
|
return response?.Pipe(Member.Parse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask<ChannelCategory> GetChannelCategoryAsync(
|
public async ValueTask<ChannelCategory> GetChannelCategoryAsync(
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Discord;
|
||||||
using DiscordChatExporter.Core.Discord.Data;
|
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using DiscordChatExporter.Core.Utils.Extensions;
|
|
||||||
using Gress;
|
using Gress;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting;
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
@ -23,21 +20,8 @@ public class ChannelExporter
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
// Build context
|
// Build context
|
||||||
var contextMembers = new Dictionary<Snowflake, Member>();
|
var context = new ExportContext(_discord, request);
|
||||||
|
await context.PopulateChannelsAndRolesAsync(cancellationToken);
|
||||||
var contextChannels = (await _discord.GetGuildChannelsAsync(request.Guild.Id, cancellationToken))
|
|
||||||
.ToDictionary(c => c.Id);
|
|
||||||
|
|
||||||
var contextRoles = (await _discord.GetGuildRolesAsync(request.Guild.Id, cancellationToken))
|
|
||||||
.ToDictionary(r => r.Id);
|
|
||||||
|
|
||||||
var context = new ExportContext(
|
|
||||||
_discord,
|
|
||||||
request,
|
|
||||||
contextMembers,
|
|
||||||
contextChannels,
|
|
||||||
contextRoles
|
|
||||||
);
|
|
||||||
|
|
||||||
// Export messages
|
// Export messages
|
||||||
await using var messageExporter = new MessageExporter(context);
|
await using var messageExporter = new MessageExporter(context);
|
||||||
|
@ -49,20 +33,9 @@ public class ChannelExporter
|
||||||
progress,
|
progress,
|
||||||
cancellationToken))
|
cancellationToken))
|
||||||
{
|
{
|
||||||
// Resolve members for referenced users
|
// Resolve members for the author and mentioned users
|
||||||
foreach (var referencedUser in message.MentionedUsers.Prepend(message.Author))
|
foreach (var user in message.MentionedUsers.Prepend(message.Author))
|
||||||
{
|
await context.PopulateMemberAsync(user.Id, cancellationToken);
|
||||||
if (contextMembers.ContainsKey(referencedUser.Id))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var member = await _discord.GetGuildMemberAsync(
|
|
||||||
request.Guild.Id,
|
|
||||||
referencedUser,
|
|
||||||
cancellationToken
|
|
||||||
);
|
|
||||||
|
|
||||||
contextMembers[member.Id] = member;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export the message
|
// Export the message
|
||||||
if (request.MessageFilter.IsMatch(message))
|
if (request.MessageFilter.IsMatch(message))
|
||||||
|
|
|
@ -12,17 +12,48 @@ using DiscordChatExporter.Core.Utils.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Exporting;
|
namespace DiscordChatExporter.Core.Exporting;
|
||||||
|
|
||||||
internal record ExportContext(
|
internal class ExportContext
|
||||||
DiscordClient Discord,
|
|
||||||
ExportRequest Request,
|
|
||||||
IReadOnlyDictionary<Snowflake, Member> Members,
|
|
||||||
IReadOnlyDictionary<Snowflake, Channel> Channels,
|
|
||||||
IReadOnlyDictionary<Snowflake, Role> Roles)
|
|
||||||
{
|
{
|
||||||
private readonly ExportAssetDownloader _assetDownloader = new(
|
private readonly Dictionary<Snowflake, Member?> _members = new();
|
||||||
Request.OutputAssetsDirPath,
|
private readonly Dictionary<Snowflake, Channel> _channels = new();
|
||||||
Request.ShouldReuseAssets
|
private readonly Dictionary<Snowflake, Role> _roles = new();
|
||||||
);
|
private readonly ExportAssetDownloader _assetDownloader;
|
||||||
|
|
||||||
|
public DiscordClient Discord { get; }
|
||||||
|
public ExportRequest Request { get; }
|
||||||
|
|
||||||
|
public ExportContext(DiscordClient discord,
|
||||||
|
ExportRequest request)
|
||||||
|
{
|
||||||
|
Discord = discord;
|
||||||
|
Request = request;
|
||||||
|
|
||||||
|
_assetDownloader = new ExportAssetDownloader(
|
||||||
|
request.OutputAssetsDirPath,
|
||||||
|
request.ShouldReuseAssets
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask PopulateChannelsAndRolesAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
await foreach (var channel in Discord.GetGuildChannelsAsync(Request.Guild.Id, cancellationToken))
|
||||||
|
_channels[channel.Id] = channel;
|
||||||
|
|
||||||
|
await foreach (var role in Discord.GetGuildRolesAsync(Request.Guild.Id, cancellationToken))
|
||||||
|
_roles[role.Id] = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because members are not pulled in bulk, we need to populate them on demand
|
||||||
|
public async ValueTask PopulateMemberAsync(Snowflake id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (_members.ContainsKey(id))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var member = await Discord.TryGetGuildMemberAsync(Request.Guild.Id, id, cancellationToken);
|
||||||
|
|
||||||
|
// Store the result even if it's null, to avoid re-fetching non-existing members
|
||||||
|
_members[id] = member;
|
||||||
|
}
|
||||||
|
|
||||||
public string FormatDate(DateTimeOffset instant) => Request.DateFormat switch
|
public string FormatDate(DateTimeOffset instant) => Request.DateFormat switch
|
||||||
{
|
{
|
||||||
|
@ -31,11 +62,11 @@ internal record ExportContext(
|
||||||
var format => instant.ToLocalString(format)
|
var format => instant.ToLocalString(format)
|
||||||
};
|
};
|
||||||
|
|
||||||
public Member? TryGetMember(Snowflake id) => Members.GetValueOrDefault(id);
|
public Member? TryGetMember(Snowflake id) => _members.GetValueOrDefault(id);
|
||||||
|
|
||||||
public Channel? TryGetChannel(Snowflake id) => Channels.GetValueOrDefault(id);
|
public Channel? TryGetChannel(Snowflake id) => _channels.GetValueOrDefault(id);
|
||||||
|
|
||||||
public Role? TryGetRole(Snowflake id) => Roles.GetValueOrDefault(id);
|
public Role? TryGetRole(Snowflake id) => _roles.GetValueOrDefault(id);
|
||||||
|
|
||||||
public Color? TryGetUserColor(Snowflake id)
|
public Color? TryGetUserColor(Snowflake id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -195,6 +195,12 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
|
||||||
}
|
}
|
||||||
else if (mention.Kind == MentionKind.User)
|
else if (mention.Kind == MentionKind.User)
|
||||||
{
|
{
|
||||||
|
// User mentions are not always included in the message object,
|
||||||
|
// which means they need to be populated on demand.
|
||||||
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/304
|
||||||
|
if (mention.TargetId is not null)
|
||||||
|
await _context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken);
|
||||||
|
|
||||||
var member = mention.TargetId?.Pipe(_context.TryGetMember);
|
var member = mention.TargetId?.Pipe(_context.TryGetMember);
|
||||||
var fullName = member?.User.FullName ?? "Unknown";
|
var fullName = member?.User.FullName ?? "Unknown";
|
||||||
var nick = member?.Nick ?? "Unknown";
|
var nick = member?.Nick ?? "Unknown";
|
||||||
|
|
|
@ -53,6 +53,12 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
|
||||||
}
|
}
|
||||||
else if (mention.Kind == MentionKind.User)
|
else if (mention.Kind == MentionKind.User)
|
||||||
{
|
{
|
||||||
|
// User mentions are not always included in the message object,
|
||||||
|
// which means they need to be populated on demand.
|
||||||
|
// https://github.com/Tyrrrz/DiscordChatExporter/issues/304
|
||||||
|
if (mention.TargetId is not null)
|
||||||
|
await _context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken);
|
||||||
|
|
||||||
var member = mention.TargetId?.Pipe(_context.TryGetMember);
|
var member = mention.TargetId?.Pipe(_context.TryGetMember);
|
||||||
var name = member?.User.Name ?? "Unknown";
|
var name = member?.User.Name ?? "Unknown";
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue