diff --git a/DiscordChatExporter/Models/Guild.cs b/DiscordChatExporter/Models/Guild.cs index 37a95ae..61d4438 100644 --- a/DiscordChatExporter/Models/Guild.cs +++ b/DiscordChatExporter/Models/Guild.cs @@ -1,8 +1,9 @@ -using Tyrrrz.Extensions; +using System.Collections.Generic; +using Tyrrrz.Extensions; namespace DiscordChatExporter.Models { - public class Guild + public partial class Guild { public string Id { get; } @@ -14,11 +15,14 @@ namespace DiscordChatExporter.Models ? $"https://cdn.discordapp.com/icons/{Id}/{IconHash}.png" : "https://cdn.discordapp.com/embed/avatars/0.png"; - public Guild(string id, string name, string iconHash) + public IReadOnlyList Roles { get; } + + public Guild(string id, string name, string iconHash, IReadOnlyList roles) { Id = id; Name = name; IconHash = iconHash; + Roles = roles; } public override string ToString() @@ -26,4 +30,9 @@ namespace DiscordChatExporter.Models return Name; } } + + public partial class Guild + { + public static Guild DirectMessages { get; } = new Guild("@me", "Direct Messages", null, new Role[0]); + } } \ No newline at end of file diff --git a/DiscordChatExporter/Services/DataService.cs b/DiscordChatExporter/Services/DataService.cs index 43affdd..a118040 100644 --- a/DiscordChatExporter/Services/DataService.cs +++ b/DiscordChatExporter/Services/DataService.cs @@ -19,6 +19,123 @@ namespace DiscordChatExporter.Services private readonly Dictionary _roleCache = new Dictionary(); private readonly Dictionary _channelCache = new Dictionary(); + private User ParseUser(JToken token) + { + var id = token.Value("id"); + var discriminator = token.Value("discriminator"); + var name = token.Value("username"); + var avatarHash = token.Value("avatar"); + + return new User(id, discriminator, name, avatarHash); + } + + private Role ParseRole(JToken token) + { + var id = token.Value("id"); + var name = token.Value("name"); + + return new Role(id, name); + } + + private Guild ParseGuild(JToken token) + { + var id = token.Value("id"); + var name = token.Value("name"); + var iconHash = token.Value("icon"); + var roles = token["roles"].Select(ParseRole).ToArray(); + + return new Guild(id, name, iconHash, roles); + } + + private Channel ParseChannel(JToken token) + { + // Get basic data + var id = token.Value("id"); + var type = (ChannelType) token.Value("type"); + + // Extract name based on type + string name; + if (type.IsEither(ChannelType.DirectTextChat, ChannelType.DirectGroupTextChat)) + { + var recipients = token["recipients"].Select(ParseUser); + name = recipients.Select(r => r.Name).JoinToString(", "); + } + else + { + name = token.Value("name"); + } + + return new Channel(id, name, type); + } + + private Message ParseMessage(JToken token) + { + // Get basic data + var id = token.Value("id"); + var timeStamp = token.Value("timestamp"); + var editedTimeStamp = token.Value("edited_timestamp"); + var content = token.Value("content"); + var type = (MessageType) token.Value("type"); + + // Workarounds for non-default types + if (type == MessageType.RecipientAdd) + content = "Added a recipient."; + else if (type == MessageType.RecipientRemove) + content = "Removed a recipient."; + else if (type == MessageType.Call) + content = "Started a call."; + else if (type == MessageType.ChannelNameChange) + content = "Changed the channel name."; + else if (type == MessageType.ChannelIconChange) + content = "Changed the channel icon."; + else if (type == MessageType.ChannelPinnedMessage) + content = "Pinned a message."; + else if (type == MessageType.GuildMemberJoin) + content = "Joined the server."; + + // Get author + var author = ParseUser(token["author"]); + + // Get attachment + var attachments = new List(); + foreach (var attachmentJson in token["attachments"].EmptyIfNull()) + { + var attachmentId = attachmentJson.Value("id"); + var attachmentUrl = attachmentJson.Value("url"); + var attachmentType = attachmentJson["width"] != null + ? AttachmentType.Image + : AttachmentType.Other; + var attachmentFileName = attachmentJson.Value("filename"); + var attachmentFileSize = attachmentJson.Value("size"); + + var attachment = new Attachment( + attachmentId, attachmentType, attachmentUrl, + attachmentFileName, attachmentFileSize); + attachments.Add(attachment); + } + + // Get user mentions + var mentionedUsers = token["mentions"].Select(ParseUser).ToArray(); + + // Get role mentions + var mentionedRoles = token["mention_roles"] + .Values() + .Select(i => _roleCache.GetOrDefault(i) ?? new Role(i, "deleted-role")) + .ToArray(); + + // Get channel mentions + var mentionedChannels = Regex.Matches(content, "<#(\\d+)>") + .Cast() + .Select(m => m.Groups[1].Value) + .ExceptBlank() + .Select(i => _channelCache.GetOrDefault(i) ?? + new Channel(i, "deleted-channel", ChannelType.GuildTextChat)) + .ToArray(); + + return new Message(id, type, author, timeStamp, editedTimeStamp, content, attachments, + mentionedUsers, mentionedRoles, mentionedChannels); + } + private async Task GetStringAsync(string url) { using (var response = await _httpClient.GetAsync(url)) @@ -33,7 +150,7 @@ namespace DiscordChatExporter.Services } } - private async Task> GetGuildRolesAsync(string token, string guildId) + public async Task GetGuildAsync(string token, string guildId) { // Form request url var url = $"{ApiRoot}/guilds/{guildId}?token={token}"; @@ -42,51 +159,59 @@ namespace DiscordChatExporter.Services var content = await GetStringAsync(url); // Parse - var roles = JToken.Parse(content)["roles"].Select(ParseRole).ToArray(); + var guild = ParseGuild(JToken.Parse(content)); - return roles; + // Add roles to cache + foreach (var role in guild.Roles) + _roleCache[role.Id] = role; + + return guild; } - public async Task> GetGuildsAsync(string token) + public async Task> GetGuildChannelsAsync(string token, string guildId) { // Form request url - var url = $"{ApiRoot}/users/@me/guilds?token={token}&limit=100"; + var url = $"{ApiRoot}/guilds/{guildId}/channels?token={token}"; // Get response var content = await GetStringAsync(url); // Parse - var guilds = JArray.Parse(content).Select(ParseGuild).ToArray(); + var channels = JArray.Parse(content).Select(ParseChannel).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) - _roleCache[role.Id] = role; - } + // Add channels to cache + foreach (var channel in channels) + _channelCache[channel.Id] = channel; - return guilds; + return channels; } - public async Task> GetDirectMessageChannelsAsync(string token) + public async Task> GetUserGuildsAsync(string token) { // Form request url - var url = $"{ApiRoot}/users/@me/channels?token={token}"; + var url = $"{ApiRoot}/users/@me/guilds?token={token}&limit=100"; // Get response var content = await GetStringAsync(url); - // Parse - var channels = JArray.Parse(content).Select(ParseChannel).ToArray(); + // Parse IDs + var guildIds = JArray.Parse(content).Select(t => t.Value("id")); - return channels; + // Get full guild infos + var guilds = new List(); + foreach (var guildId in guildIds) + { + var guild = await GetGuildAsync(token, guildId); + guilds.Add(guild); + } + + return guilds; } - public async Task> GetGuildChannelsAsync(string token, string guildId) + public async Task> GetDirectMessageChannelsAsync(string token) { // Form request url - var url = $"{ApiRoot}/guilds/{guildId}/channels?token={token}"; + var url = $"{ApiRoot}/users/@me/channels?token={token}"; // Get response var content = await GetStringAsync(url); @@ -94,10 +219,6 @@ namespace DiscordChatExporter.Services // Parse var channels = JArray.Parse(content).Select(ParseChannel).ToArray(); - // Cache - foreach (var channel in channels) - _channelCache[channel.Id] = channel; - return channels; } @@ -120,7 +241,7 @@ namespace DiscordChatExporter.Services var content = await GetStringAsync(url); // Parse - var messages = JArray.Parse(content).Select(j => ParseMessage(j, _roleCache, _channelCache)); + var messages = JArray.Parse(content).Select(ParseMessage); // Add messages to list string currentMessageId = null; @@ -176,121 +297,5 @@ namespace DiscordChatExporter.Services var value = ((ulong) unixTime - 1420070400000UL) << 22; return value.ToString(); } - - private static Guild ParseGuild(JToken token) - { - var id = token.Value("id"); - var name = token.Value("name"); - var iconHash = token.Value("icon"); - - return new Guild(id, name, iconHash); - } - - private static User ParseUser(JToken token) - { - var id = token.Value("id"); - var discriminator = token.Value("discriminator"); - var name = token.Value("username"); - var avatarHash = token.Value("avatar"); - - return new User(id, discriminator, name, avatarHash); - } - - private static Role ParseRole(JToken token) - { - var id = token.Value("id"); - var name = token.Value("name"); - - return new Role(id, name); - } - - private static Channel ParseChannel(JToken token) - { - // Get basic data - var id = token.Value("id"); - var type = (ChannelType) token.Value("type"); - - // Extract name based on type - string name; - if (type.IsEither(ChannelType.DirectTextChat, ChannelType.DirectGroupTextChat)) - { - var recipients = token["recipients"].Select(ParseUser); - name = recipients.Select(r => r.Name).JoinToString(", "); - } - else - { - name = token.Value("name"); - } - - return new Channel(id, name, type); - } - - private static Message ParseMessage(JToken token, - IDictionary roles, IDictionary channels) - { - // Get basic data - var id = token.Value("id"); - var timeStamp = token.Value("timestamp"); - var editedTimeStamp = token.Value("edited_timestamp"); - var content = token.Value("content"); - var type = (MessageType) token.Value("type"); - - // Workarounds for non-default types - if (type == MessageType.RecipientAdd) - content = "Added a recipient."; - else if (type == MessageType.RecipientRemove) - content = "Removed a recipient."; - else if (type == MessageType.Call) - content = "Started a call."; - else if (type == MessageType.ChannelNameChange) - content = "Changed the channel name."; - else if (type == MessageType.ChannelIconChange) - content = "Changed the channel icon."; - else if (type == MessageType.ChannelPinnedMessage) - content = "Pinned a message."; - else if (type == MessageType.GuildMemberJoin) - content = "Joined the server."; - - // Get author - var author = ParseUser(token["author"]); - - // Get attachment - var attachments = new List(); - foreach (var attachmentJson in token["attachments"].EmptyIfNull()) - { - var attachmentId = attachmentJson.Value("id"); - var attachmentUrl = attachmentJson.Value("url"); - var attachmentType = attachmentJson["width"] != null - ? AttachmentType.Image - : AttachmentType.Other; - var attachmentFileName = attachmentJson.Value("filename"); - var attachmentFileSize = attachmentJson.Value("size"); - - var attachment = new Attachment( - attachmentId, attachmentType, attachmentUrl, - attachmentFileName, attachmentFileSize); - attachments.Add(attachment); - } - - // Get user mentions - var mentionedUsers = token["mentions"].Select(ParseUser).ToArray(); - - // Get role mentions - var mentionedRoles = token["mention_roles"] - .Values() - .Select(i => roles.GetOrDefault(i) ?? new Role(i, "deleted-role")) - .ToArray(); - - // Get channel mentions - var mentionedChanenls = Regex.Matches(content, "<#(\\d+)>") - .Cast() - .Select(m => m.Groups[1].Value) - .ExceptBlank() - .Select(i => channels.GetOrDefault(i) ?? new Channel(i, "deleted-channel", ChannelType.GuildTextChat)) - .ToArray(); - - return new Message(id, type, author, timeStamp, editedTimeStamp, content, attachments, - mentionedUsers, mentionedRoles, mentionedChanenls); - } } } \ No newline at end of file diff --git a/DiscordChatExporter/Services/IDataService.cs b/DiscordChatExporter/Services/IDataService.cs index 25cf794..3d037b7 100644 --- a/DiscordChatExporter/Services/IDataService.cs +++ b/DiscordChatExporter/Services/IDataService.cs @@ -7,12 +7,14 @@ namespace DiscordChatExporter.Services { public interface IDataService { - Task> GetGuildsAsync(string token); - - Task> GetDirectMessageChannelsAsync(string token); + Task GetGuildAsync(string token, string guildId); Task> GetGuildChannelsAsync(string token, string guildId); + Task> GetUserGuildsAsync(string token); + + Task> GetDirectMessageChannelsAsync(string token); + Task> GetChannelMessagesAsync(string token, string channelId, DateTime? from, DateTime? to); } diff --git a/DiscordChatExporter/ViewModels/MainViewModel.cs b/DiscordChatExporter/ViewModels/MainViewModel.cs index 45cc58b..2473bcf 100644 --- a/DiscordChatExporter/ViewModels/MainViewModel.cs +++ b/DiscordChatExporter/ViewModels/MainViewModel.cs @@ -130,13 +130,13 @@ namespace DiscordChatExporter.ViewModels // Get DM channels { var channels = await _dataService.GetDirectMessageChannelsAsync(token); - var guild = new Guild("@me", "Direct Messages", null); + var guild = Guild.DirectMessages; _guildChannelsMap[guild] = channels.ToArray(); } // Get guild channels { - var guilds = await _dataService.GetGuildsAsync(token); + var guilds = await _dataService.GetUserGuildsAsync(token); foreach (var guild in guilds) { var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id);