mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-06-04 08:29:45 -04:00
Add support for Guild Member object & included data (#279)
This commit is contained in:
parent
7bb8038ce9
commit
79e43c4144
10 changed files with 118 additions and 29 deletions
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CliFx;
|
using CliFx;
|
||||||
using DiscordChatExporter.Cli.Commands;
|
using DiscordChatExporter.Cli.Commands;
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
namespace DiscordChatExporter.Core.Models
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Models
|
||||||
{
|
{
|
||||||
// https://discordapp.string.IsNullOrWhiteSpace(com/developers/docs/resources/guild#guild-object
|
// https://discordapp.string.IsNullOrWhiteSpace(com/developers/docs/resources/guild#guild-object
|
||||||
|
|
||||||
|
@ -10,13 +15,19 @@
|
||||||
|
|
||||||
public string? IconHash { get; }
|
public string? IconHash { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<Role> Roles { get; }
|
||||||
|
|
||||||
|
public Dictionary<string, Member?> Members { get; }
|
||||||
|
|
||||||
public string IconUrl { get; }
|
public string IconUrl { get; }
|
||||||
|
|
||||||
public Guild(string id, string name, string? iconHash)
|
public Guild(string id, string name, IReadOnlyList<Role> roles, string? iconHash)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
IconHash = iconHash;
|
IconHash = iconHash;
|
||||||
|
Roles = roles;
|
||||||
|
Members = new Dictionary<string, Member?>();
|
||||||
|
|
||||||
IconUrl = GetIconUrl(id, iconHash);
|
IconUrl = GetIconUrl(id, iconHash);
|
||||||
}
|
}
|
||||||
|
@ -26,6 +37,16 @@
|
||||||
|
|
||||||
public partial class Guild
|
public partial class Guild
|
||||||
{
|
{
|
||||||
|
public static string GetUserColor(Guild guild, User user) =>
|
||||||
|
guild.Members.GetValueOrDefault(user.Id, null)?.Roles
|
||||||
|
?.Select(r => guild.Roles
|
||||||
|
.Where(role => r == role.Id)
|
||||||
|
.FirstOrDefault()
|
||||||
|
)?.Where(r => r.Color != Color.Black)?
|
||||||
|
.Aggregate<Role, Role?>(null, (a, b) => (a?.Position ?? 0) > b.Position? a : b)?
|
||||||
|
.ColorAsHex ?? "";
|
||||||
|
public static string GetUserNick(Guild guild, User user) => guild.Members.GetValueOrDefault(user.Id)?.Nick ?? user.Name;
|
||||||
|
|
||||||
public static string GetIconUrl(string id, string? iconHash)
|
public static string GetIconUrl(string id, string? iconHash)
|
||||||
{
|
{
|
||||||
return !string.IsNullOrWhiteSpace(iconHash)
|
return !string.IsNullOrWhiteSpace(iconHash)
|
||||||
|
@ -33,6 +54,6 @@
|
||||||
: "https://cdn.discordapp.com/embed/avatars/0.png";
|
: "https://cdn.discordapp.com/embed/avatars/0.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Guild DirectMessages { get; } = new Guild("@me", "Direct Messages", null);
|
public static Guild DirectMessages { get; } = new Guild("@me", "Direct Messages", Array.Empty<Role>(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
20
DiscordChatExporter.Core.Models/Member.cs
Normal file
20
DiscordChatExporter.Core.Models/Member.cs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Models
|
||||||
|
{
|
||||||
|
public class Member
|
||||||
|
{
|
||||||
|
public string UserId { get; }
|
||||||
|
public string? Nick { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<string> Roles { get; }
|
||||||
|
|
||||||
|
public Member(string userId, string? nick, IReadOnlyList<string> roles)
|
||||||
|
{
|
||||||
|
UserId = userId;
|
||||||
|
Nick = nick;
|
||||||
|
Roles = roles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,7 @@
|
||||||
namespace DiscordChatExporter.Core.Models
|
|
||||||
|
using System.Drawing;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Core.Models
|
||||||
{
|
{
|
||||||
// https://discordapp.com/developers/docs/topics/permissions#role-object
|
// https://discordapp.com/developers/docs/topics/permissions#role-object
|
||||||
|
|
||||||
|
@ -8,10 +11,19 @@
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public Role(string id, string name)
|
public Color Color { get; }
|
||||||
|
|
||||||
|
public string ColorAsHex => $"#{(Color.ToArgb() & 0xffffff):X6}";
|
||||||
|
public string ColorAsRgb => $"{Color.R}, {Color.G}, {Color.B}";
|
||||||
|
|
||||||
|
public int Position { get; }
|
||||||
|
|
||||||
|
public Role(string id, string name, Color color, int position)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
|
Color = color;
|
||||||
|
Position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
@ -19,6 +31,6 @@
|
||||||
|
|
||||||
public partial class Role
|
public partial class Role
|
||||||
{
|
{
|
||||||
public static Role CreateDeletedRole(string id) => new Role(id, "deleted-role");
|
public static Role CreateDeletedRole(string id) => new Role(id, "deleted-role", Color.Black, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -75,6 +75,10 @@ namespace DiscordChatExporter.Core.Rendering.Formatters
|
||||||
scriptObject.Import("FormatMarkdown",
|
scriptObject.Import("FormatMarkdown",
|
||||||
new Func<string, string>(m => HtmlRenderingLogic.FormatMarkdown(Context, m)));
|
new Func<string, string>(m => HtmlRenderingLogic.FormatMarkdown(Context, m)));
|
||||||
|
|
||||||
|
scriptObject.Import("GetUserColor", new Func<Guild, User, string>(Guild.GetUserColor));
|
||||||
|
|
||||||
|
scriptObject.Import("GetUserNick", new Func<Guild, User, string>(Guild.GetUserNick));
|
||||||
|
|
||||||
// Push model
|
// Push model
|
||||||
templateContext.PushGlobal(scriptObject);
|
templateContext.PushGlobal(scriptObject);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -114,8 +115,9 @@ namespace DiscordChatExporter.Core.Rendering.Logic
|
||||||
{
|
{
|
||||||
var role = context.MentionableRoles.FirstOrDefault(r => r.Id == mentionNode.Id) ??
|
var role = context.MentionableRoles.FirstOrDefault(r => r.Id == mentionNode.Id) ??
|
||||||
Role.CreateDeletedRole(mentionNode.Id);
|
Role.CreateDeletedRole(mentionNode.Id);
|
||||||
|
string style = "";
|
||||||
return $"<span class=\"mention\">@{HtmlEncode(role.Name)}</span>";
|
if (role.Color != Color.Black) style = $"style=\"color: {role.ColorAsHex}; background-color: rgba({role.ColorAsRgb}, 0.1); font-weight: 400;\"";
|
||||||
|
return $"<span class=\"mention\" {style}>@{HtmlEncode(role.Name)}</span>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="chatlog__messages">
|
<div class="chatlog__messages">
|
||||||
{{~ # Author name and timestamp ~}}
|
{{~ # Author name and timestamp ~}}
|
||||||
<span class="chatlog__author-name" title="{{ MessageGroup.Author.FullName | html.escape }}" data-user-id="{{ MessageGroup.Author.Id | html.escape }}">{{ MessageGroup.Author.Name | html.escape }}</span>
|
<span class="chatlog__author-name" title="{{ MessageGroup.Author.FullName | html.escape }}" data-user-id="{{ MessageGroup.Author.Id | html.escape }}" style="color: {{ GetUserColor Context.Guild MessageGroup.Author }}">{{ GetUserNick Context.Guild MessageGroup.Author | html.escape }}</span>
|
||||||
|
|
||||||
{{~ # Bot tag ~}}
|
{{~ # Bot tag ~}}
|
||||||
{{~ if MessageGroup.Author.IsBot ~}}
|
{{~ if MessageGroup.Author.IsBot ~}}
|
||||||
|
|
|
@ -21,13 +21,23 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return new User(id, discriminator, name, avatarHash, isBot);
|
return new User(id, discriminator, name, avatarHash, isBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Member ParseMember(JToken json)
|
||||||
|
{
|
||||||
|
var userId = ParseUser(json["user"]!).Id;
|
||||||
|
var nick = json["nick"]?.Value<string>();
|
||||||
|
var roles = json["roles"]!.Select(jt => jt.Value<string>()).ToArray();
|
||||||
|
|
||||||
|
return new Member(userId, nick, roles);
|
||||||
|
}
|
||||||
|
|
||||||
private Guild ParseGuild(JToken json)
|
private Guild ParseGuild(JToken json)
|
||||||
{
|
{
|
||||||
var id = json["id"]!.Value<string>();
|
var id = json["id"]!.Value<string>();
|
||||||
var name = json["name"]!.Value<string>();
|
var name = json["name"]!.Value<string>();
|
||||||
var iconHash = json["icon"]!.Value<string>();
|
var iconHash = json["icon"]!.Value<string>();
|
||||||
|
var roles = json["roles"]!.Select(ParseRole).ToList();
|
||||||
|
|
||||||
return new Guild(id, name, iconHash);
|
return new Guild(id, name, roles, iconHash);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Channel ParseChannel(JToken json)
|
private Channel ParseChannel(JToken json)
|
||||||
|
@ -64,8 +74,10 @@ namespace DiscordChatExporter.Core.Services
|
||||||
{
|
{
|
||||||
var id = json["id"]!.Value<string>();
|
var id = json["id"]!.Value<string>();
|
||||||
var name = json["name"]!.Value<string>();
|
var name = json["name"]!.Value<string>();
|
||||||
|
var color = json["color"]!.Value<int>();
|
||||||
|
var position = json["position"]!.Value<int>();
|
||||||
|
|
||||||
return new Role(id, name);
|
return new Role(id, name, Color.FromArgb(color), position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Attachment ParseAttachment(JToken json)
|
private Attachment ParseAttachment(JToken json)
|
||||||
|
|
|
@ -42,6 +42,10 @@ namespace DiscordChatExporter.Core.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<JToken> GetApiResponseAsync(AuthToken token, string route)
|
private async Task<JToken> GetApiResponseAsync(AuthToken token, string route)
|
||||||
|
{
|
||||||
|
return (await GetApiResponseAsync(token, route, true))!;
|
||||||
|
}
|
||||||
|
private async Task<JToken?> GetApiResponseAsync(AuthToken token, string route, bool errorOnFail)
|
||||||
{
|
{
|
||||||
using var response = await _httpPolicy.ExecuteAsync(async () =>
|
using var response = await _httpPolicy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
|
@ -56,7 +60,10 @@ namespace DiscordChatExporter.Core.Services
|
||||||
|
|
||||||
// We throw our own exception here because default one doesn't have status code
|
// We throw our own exception here because default one doesn't have status code
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
throw new HttpErrorStatusCodeException(response.StatusCode, response.ReasonPhrase);
|
{
|
||||||
|
if(errorOnFail) throw new HttpErrorStatusCodeException(response.StatusCode, response.ReasonPhrase);
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
var jsonRaw = await response.Content.ReadAsStringAsync();
|
var jsonRaw = await response.Content.ReadAsStringAsync();
|
||||||
return JToken.Parse(jsonRaw);
|
return JToken.Parse(jsonRaw);
|
||||||
|
@ -74,6 +81,15 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return guild;
|
return guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Member?> GetGuildMemberAsync(AuthToken token, string guildId, string userId)
|
||||||
|
{
|
||||||
|
var response = await GetApiResponseAsync(token, $"guilds/{guildId}/members/{userId}", false);
|
||||||
|
if(response == null) return null;
|
||||||
|
var member = ParseMember(response);
|
||||||
|
|
||||||
|
return member;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<Channel> GetChannelAsync(AuthToken token, string channelId)
|
public async Task<Channel> GetChannelAsync(AuthToken token, string channelId)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, $"channels/{channelId}");
|
var response = await GetApiResponseAsync(token, $"channels/{channelId}");
|
||||||
|
@ -125,18 +141,6 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Role>> GetGuildRolesAsync(AuthToken token, string guildId)
|
|
||||||
{
|
|
||||||
// Special case for direct messages pseudo-guild
|
|
||||||
if (guildId == Guild.DirectMessages.Id)
|
|
||||||
return Array.Empty<Role>();
|
|
||||||
|
|
||||||
var response = await GetApiResponseAsync(token, $"guilds/{guildId}/roles");
|
|
||||||
var roles = response.Select(ParseRole).ToArray();
|
|
||||||
|
|
||||||
return roles;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Message> GetLastMessageAsync(AuthToken token, string channelId, DateTimeOffset? before = null)
|
private async Task<Message> GetLastMessageAsync(AuthToken token, string channelId, DateTimeOffset? before = null)
|
||||||
{
|
{
|
||||||
var route = $"channels/{channelId}/messages?limit=1";
|
var route = $"channels/{channelId}/messages?limit=1";
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
// Create context
|
// Create context
|
||||||
var mentionableUsers = new HashSet<User>(IdBasedEqualityComparer.Instance);
|
var mentionableUsers = new HashSet<User>(IdBasedEqualityComparer.Instance);
|
||||||
var mentionableChannels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
var mentionableChannels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
||||||
var mentionableRoles = await _dataService.GetGuildRolesAsync(token, guild.Id);
|
var mentionableRoles = guild.Roles;
|
||||||
|
|
||||||
var context = new RenderContext
|
var context = new RenderContext
|
||||||
(
|
(
|
||||||
|
@ -50,8 +50,21 @@ namespace DiscordChatExporter.Core.Services
|
||||||
await foreach (var message in _dataService.GetMessagesAsync(token, channel.Id, after, before, progress))
|
await foreach (var message in _dataService.GetMessagesAsync(token, channel.Id, after, before, progress))
|
||||||
{
|
{
|
||||||
// Add encountered users to the list of mentionable users
|
// Add encountered users to the list of mentionable users
|
||||||
mentionableUsers.Add(message.Author);
|
var encounteredUsers = new List<User>();
|
||||||
mentionableUsers.AddRange(message.MentionedUsers);
|
encounteredUsers.Add(message.Author);
|
||||||
|
encounteredUsers.AddRange(message.MentionedUsers);
|
||||||
|
|
||||||
|
mentionableUsers.AddRange(encounteredUsers);
|
||||||
|
|
||||||
|
foreach (User u in encounteredUsers)
|
||||||
|
{
|
||||||
|
if(!guild.Members.ContainsKey(u.Id))
|
||||||
|
{
|
||||||
|
var member = await _dataService.GetGuildMemberAsync(token, guild.Id, u.Id);
|
||||||
|
guild.Members[u.Id] = member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Render message
|
// Render message
|
||||||
await renderer.RenderMessageAsync(message);
|
await renderer.RenderMessageAsync(message);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue