mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-22 19:05:09 -04:00
Add mention support (#17)
* Support for user mentions and incomplete support for role mentions * Improve formatting a bit * Implement a hack to get roles
This commit is contained in:
parent
171718989a
commit
4d5118de76
7 changed files with 146 additions and 12 deletions
|
@ -92,6 +92,7 @@
|
|||
<Compile Include="Models\ChannelType.cs" />
|
||||
<Compile Include="Models\ExportFormat.cs" />
|
||||
<Compile Include="Models\Extensions.cs" />
|
||||
<Compile Include="Models\Role.cs" />
|
||||
<Compile Include="Models\MessageType.cs" />
|
||||
<Compile Include="Services\IMessageGroupService.cs" />
|
||||
<Compile Include="Services\MessageGroupService.cs" />
|
||||
|
|
|
@ -17,9 +17,14 @@ namespace DiscordChatExporter.Models
|
|||
|
||||
public IReadOnlyList<Attachment> Attachments { get; }
|
||||
|
||||
public IReadOnlyList<User> MentionedUsers { get; }
|
||||
|
||||
public IReadOnlyList<Role> MentionedRoles { get; }
|
||||
|
||||
public Message(string id, User author,
|
||||
DateTime timeStamp, DateTime? editedTimeStamp,
|
||||
string content, IReadOnlyList<Attachment> attachments)
|
||||
string content, IReadOnlyList<Attachment> attachments,
|
||||
IReadOnlyList<User> mentionedUsers, IReadOnlyList<Role> mentionedRoles)
|
||||
{
|
||||
Id = id;
|
||||
Author = author;
|
||||
|
@ -27,6 +32,8 @@ namespace DiscordChatExporter.Models
|
|||
EditedTimeStamp = editedTimeStamp;
|
||||
Content = content;
|
||||
Attachments = attachments;
|
||||
MentionedUsers = mentionedUsers;
|
||||
MentionedRoles = mentionedRoles;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
|
20
DiscordChatExporter/Models/Role.cs
Normal file
20
DiscordChatExporter/Models/Role.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
namespace DiscordChatExporter.Models
|
||||
{
|
||||
public class Role
|
||||
{
|
||||
public string Id { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public Role(string id, string name)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -122,4 +122,8 @@ img.emoji {
|
|||
height: 24px;
|
||||
width: 24px;
|
||||
vertical-align: -.4em;
|
||||
}
|
||||
|
||||
span.mention {
|
||||
font-weight: 600;
|
||||
}
|
|
@ -122,4 +122,8 @@ img.emoji {
|
|||
height: 24px;
|
||||
width: 24px;
|
||||
vertical-align: -.4em;
|
||||
}
|
||||
|
||||
span.mention {
|
||||
font-weight: 600;
|
||||
}
|
|
@ -13,7 +13,9 @@ namespace DiscordChatExporter.Services
|
|||
public partial class DataService : IDataService, IDisposable
|
||||
{
|
||||
private const string ApiRoot = "https://discordapp.com/api/v6";
|
||||
|
||||
private readonly HttpClient _httpClient = new HttpClient();
|
||||
private readonly Dictionary<string, Role> _rolesCache = new Dictionary<string, Role>();
|
||||
|
||||
private async Task<string> GetStringAsync(string url)
|
||||
{
|
||||
|
@ -29,6 +31,20 @@ namespace DiscordChatExporter.Services
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<IReadOnlyList<Role>> GetGuildRolesAsync(string token, string guildId)
|
||||
{
|
||||
// Form request url
|
||||
var url = $"{ApiRoot}/guilds/{guildId}?token={token}";
|
||||
|
||||
// Get response
|
||||
var content = await GetStringAsync(url);
|
||||
|
||||
// Parse
|
||||
var roles = JToken.Parse(content)["roles"].Select(ParseRole).ToArray();
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<Guild>> GetGuildsAsync(string token)
|
||||
{
|
||||
// Form request url
|
||||
|
@ -40,6 +56,14 @@ namespace DiscordChatExporter.Services
|
|||
// Parse
|
||||
var guilds = JArray.Parse(content).Select(ParseGuild).ToArray();
|
||||
|
||||
// HACK: also get roles for all of them
|
||||
foreach (var guild in guilds)
|
||||
{
|
||||
var roles = await GetGuildRolesAsync(token, guild.Id);
|
||||
foreach (var role in roles)
|
||||
_rolesCache[role.Id] = role;
|
||||
}
|
||||
|
||||
return guilds;
|
||||
}
|
||||
|
||||
|
@ -90,7 +114,7 @@ namespace DiscordChatExporter.Services
|
|||
var content = await GetStringAsync(url);
|
||||
|
||||
// Parse
|
||||
var messages = JArray.Parse(content).Select(ParseMessage);
|
||||
var messages = JArray.Parse(content).Select(j => ParseMessage(j, _rolesCache));
|
||||
|
||||
// Add messages to list
|
||||
string currentMessageId = null;
|
||||
|
@ -141,6 +165,15 @@ namespace DiscordChatExporter.Services
|
|||
|
||||
public partial class DataService
|
||||
{
|
||||
private static Guild ParseGuild(JToken token)
|
||||
{
|
||||
var id = token.Value<string>("id");
|
||||
var name = token.Value<string>("name");
|
||||
var iconHash = token.Value<string>("icon");
|
||||
|
||||
return new Guild(id, name, iconHash);
|
||||
}
|
||||
|
||||
private static User ParseUser(JToken token)
|
||||
{
|
||||
var id = token.Value<string>("id");
|
||||
|
@ -151,13 +184,12 @@ namespace DiscordChatExporter.Services
|
|||
return new User(id, discriminator, name, avatarHash);
|
||||
}
|
||||
|
||||
private static Guild ParseGuild(JToken token)
|
||||
private static Role ParseRole(JToken token)
|
||||
{
|
||||
var id = token.Value<string>("id");
|
||||
var name = token.Value<string>("name");
|
||||
var iconHash = token.Value<string>("icon");
|
||||
|
||||
return new Guild(id, name, iconHash);
|
||||
return new Role(id, name);
|
||||
}
|
||||
|
||||
private static Channel ParseChannel(JToken token)
|
||||
|
@ -181,7 +213,7 @@ namespace DiscordChatExporter.Services
|
|||
return new Channel(id, name, type);
|
||||
}
|
||||
|
||||
private static Message ParseMessage(JToken token)
|
||||
private static Message ParseMessage(JToken token, IDictionary<string, Role> roles)
|
||||
{
|
||||
// Get basic data
|
||||
var id = token.Value<string>("id");
|
||||
|
@ -227,7 +259,15 @@ namespace DiscordChatExporter.Services
|
|||
attachments.Add(attachment);
|
||||
}
|
||||
|
||||
return new Message(id, author, timeStamp, editedTimeStamp, content, attachments);
|
||||
// Get mentions
|
||||
var mentionedUsers = token["mentions"].Select(ParseUser).ToArray();
|
||||
var mentionedRoles = token["mention_roles"]
|
||||
.Values<string>()
|
||||
.Select(i => roles.GetOrDefault(i) ?? new Role(i, "deleted-role"))
|
||||
.ToArray();
|
||||
|
||||
return new Message(id, author, timeStamp, editedTimeStamp, content, attachments,
|
||||
mentionedUsers, mentionedRoles);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
@ -46,7 +47,7 @@ namespace DiscordChatExporter.Services
|
|||
// Content
|
||||
if (message.Content.IsNotBlank())
|
||||
{
|
||||
var contentFormatted = message.Content.Replace("\n", Environment.NewLine);
|
||||
var contentFormatted = FormatMessageContentText(message);
|
||||
await writer.WriteLineAsync(contentFormatted);
|
||||
}
|
||||
|
||||
|
@ -119,7 +120,7 @@ namespace DiscordChatExporter.Services
|
|||
if (message.Content.IsNotBlank())
|
||||
{
|
||||
await writer.WriteLineAsync("<div class=\"msg-content\">");
|
||||
var contentFormatted = FormatMessageContentHtml(message.Content);
|
||||
var contentFormatted = FormatMessageContentHtml(message);
|
||||
await writer.WriteAsync(contentFormatted);
|
||||
|
||||
// Edited timestamp
|
||||
|
@ -215,8 +216,39 @@ namespace DiscordChatExporter.Services
|
|||
return $"{size:0.#} {units[unit]}";
|
||||
}
|
||||
|
||||
private static string FormatMessageContentHtml(string content)
|
||||
public static string FormatMessageContentText(Message message)
|
||||
{
|
||||
var content = message.Content;
|
||||
|
||||
// New lines
|
||||
content = content.Replace("\n", Environment.NewLine);
|
||||
|
||||
// User mentions (<@id>)
|
||||
content = Regex.Replace(content, "<@(\\d*)>",
|
||||
m =>
|
||||
{
|
||||
var mentionedUser = message.MentionedUsers.First(u => u.Id == m.Groups[1].Value);
|
||||
return $"@{mentionedUser}";
|
||||
});
|
||||
|
||||
// Role mentions (<@&id>)
|
||||
content = Regex.Replace(content, "<@&(\\d*)>",
|
||||
m =>
|
||||
{
|
||||
var mentionedRole = message.MentionedRoles.First(r => r.Id == m.Groups[1].Value);
|
||||
return $"@{mentionedRole.Name}";
|
||||
});
|
||||
|
||||
// Custom emojis (<:name:id>)
|
||||
content = Regex.Replace(content, "<(:.*?:)\\d*>", "$1");
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private static string FormatMessageContentHtml(Message message)
|
||||
{
|
||||
var content = message.Content;
|
||||
|
||||
// Encode HTML
|
||||
content = HtmlEncode(content);
|
||||
|
||||
|
@ -248,9 +280,35 @@ namespace DiscordChatExporter.Services
|
|||
// New lines
|
||||
content = content.Replace("\n", "<br />");
|
||||
|
||||
// Meta mentions (@everyone)
|
||||
content = content.Replace("@everyone", "<span class=\"mention\">@everyone</span>");
|
||||
|
||||
// Meta mentions (@here)
|
||||
content = content.Replace("@here", "<span class=\"mention\">@here</span>");
|
||||
|
||||
// User mentions (<@id>)
|
||||
content = Regex.Replace(content, "<@(\\d*)>",
|
||||
m =>
|
||||
{
|
||||
var mentionedUser = message.MentionedUsers.First(u => u.Id == m.Groups[1].Value);
|
||||
return $"<span class=\"mention\" title=\"{HtmlEncode(mentionedUser)}\">" +
|
||||
$"@{HtmlEncode(mentionedUser.Name)}" +
|
||||
"</span>";
|
||||
});
|
||||
|
||||
// Role mentions (<@&id>)
|
||||
content = Regex.Replace(content, "<@&(\\d*)>",
|
||||
m =>
|
||||
{
|
||||
var mentionedRole = message.MentionedRoles.First(r => r.Id == m.Groups[1].Value);
|
||||
return "<span class=\"mention\">" +
|
||||
$"@{HtmlEncode(mentionedRole.Name)}" +
|
||||
"</span>";
|
||||
});
|
||||
|
||||
// Custom emojis (<:name:id>)
|
||||
content = Regex.Replace(content, "<:.*?:(\\d+)>",
|
||||
"<img class=\"emoji\" src=\"https://cdn.discordapp.com/emojis/$1.png\" />");
|
||||
content = Regex.Replace(content, "<(:.*?:)(\\d*)>",
|
||||
"<img class=\"emoji\" title=\"$1\" src=\"https://cdn.discordapp.com/emojis/$2.png\" />");
|
||||
|
||||
return content;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue