Add mention support (#17)

* Support for user mentions and incomplete support for role mentions

* Improve formatting a bit

* Implement a hack to get roles
pull/26/head
Alexey Golub 7 years ago committed by GitHub
parent 171718989a
commit 4d5118de76

@ -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()

@ -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;
}
}
}

@ -123,3 +123,7 @@ img.emoji {
width: 24px;
vertical-align: -.4em;
}
span.mention {
font-weight: 600;
}

@ -123,3 +123,7 @@ img.emoji {
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, "&lt;@(\\d*)&gt;",
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, "&lt;@&amp;(\\d*)&gt;",
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, "&lt;:.*?:(\\d+)&gt;",
"<img class=\"emoji\" src=\"https://cdn.discordapp.com/emojis/$1.png\" />");
content = Regex.Replace(content, "&lt;(:.*?:)(\\d*)&gt;",
"<img class=\"emoji\" title=\"$1\" src=\"https://cdn.discordapp.com/emojis/$2.png\" />");
return content;
}

Loading…
Cancel
Save