From a2c8cbb5605bdc9783c2441265ebed65633483a1 Mon Sep 17 00:00:00 2001 From: Ske Date: Fri, 25 Dec 2020 13:19:35 +0100 Subject: [PATCH] Add DM support --- Myriad/Cache/DiscordCacheExtensions.cs | 11 ----- Myriad/Cache/IDiscordCache.cs | 1 + Myriad/Cache/MemoryDiscordCache.cs | 38 ++++++++++------- Myriad/Extensions/CacheExtensions.cs | 23 ++++++++++ Myriad/Rest/DiscordApiClient.cs | 3 ++ Myriad/Rest/Types/Requests/CreateDmRequest.cs | 4 ++ Myriad/Types/Channel.cs | 1 + .../ContextEntityArgumentsExt.cs | 1 + PluralKit.Bot/Commands/Token.cs | 42 ++++++++++--------- 9 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 Myriad/Rest/Types/Requests/CreateDmRequest.cs diff --git a/Myriad/Cache/DiscordCacheExtensions.cs b/Myriad/Cache/DiscordCacheExtensions.cs index b4165987..e50c3453 100644 --- a/Myriad/Cache/DiscordCacheExtensions.cs +++ b/Myriad/Cache/DiscordCacheExtensions.cs @@ -55,16 +55,5 @@ namespace Myriad.Cache foreach (var mention in evt.Mentions) await cache.SaveUser(mention); } - - public static async ValueTask GetOrFetchUser(this IDiscordCache cache, DiscordApiClient rest, ulong userId) - { - if (cache.TryGetUser(userId, out var cacheUser)) - return cacheUser; - - var restUser = await rest.GetUser(userId); - if (restUser != null) - await cache.SaveUser(restUser); - return restUser; - } } } \ No newline at end of file diff --git a/Myriad/Cache/IDiscordCache.cs b/Myriad/Cache/IDiscordCache.cs index 7c72b272..c778ed32 100644 --- a/Myriad/Cache/IDiscordCache.cs +++ b/Myriad/Cache/IDiscordCache.cs @@ -19,6 +19,7 @@ namespace Myriad.Cache public bool TryGetGuild(ulong guildId, out Guild guild); public bool TryGetChannel(ulong channelId, out Channel channel); + public bool TryGetDmChannel(ulong userId, out Channel channel); public bool TryGetUser(ulong userId, out User user); public bool TryGetRole(ulong roleId, out Role role); diff --git a/Myriad/Cache/MemoryDiscordCache.cs b/Myriad/Cache/MemoryDiscordCache.cs index 2a6c194f..2dcfde6a 100644 --- a/Myriad/Cache/MemoryDiscordCache.cs +++ b/Myriad/Cache/MemoryDiscordCache.cs @@ -10,19 +10,12 @@ namespace Myriad.Cache { public class MemoryDiscordCache: IDiscordCache { - private readonly ConcurrentDictionary _channels; - private readonly ConcurrentDictionary _guilds; - private readonly ConcurrentDictionary _roles; - private readonly ConcurrentDictionary _users; - - public MemoryDiscordCache() - { - _guilds = new ConcurrentDictionary(); - _channels = new ConcurrentDictionary(); - _users = new ConcurrentDictionary(); - _roles = new ConcurrentDictionary(); - } - + private readonly ConcurrentDictionary _channels = new(); + private readonly ConcurrentDictionary _dmChannels = new(); + private readonly ConcurrentDictionary _guilds = new(); + private readonly ConcurrentDictionary _roles = new(); + private readonly ConcurrentDictionary _users = new(); + public ValueTask SaveGuild(Guild guild) { SaveGuildRaw(guild); @@ -35,14 +28,21 @@ namespace Myriad.Cache return default; } - public ValueTask SaveChannel(Channel channel) + public async ValueTask SaveChannel(Channel channel) { _channels[channel.Id] = channel; if (channel.GuildId != null && _guilds.TryGetValue(channel.GuildId.Value, out var guild)) guild.Channels.TryAdd(channel.Id, true); - return default; + if (channel.Recipients != null) + { + foreach (var recipient in channel.Recipients) + { + _dmChannels[recipient.Id] = channel.Id; + await SaveUser(recipient); + } + } } public ValueTask SaveUser(User user) @@ -125,6 +125,14 @@ namespace Myriad.Cache public bool TryGetChannel(ulong channelId, out Channel channel) => _channels.TryGetValue(channelId, out channel!); + public bool TryGetDmChannel(ulong userId, out Channel channel) + { + channel = default!; + if (!_dmChannels.TryGetValue(userId, out var channelId)) + return false; + return TryGetChannel(channelId, out channel); + } + public bool TryGetUser(ulong userId, out User user) => _users.TryGetValue(userId, out user!); diff --git a/Myriad/Extensions/CacheExtensions.cs b/Myriad/Extensions/CacheExtensions.cs index 260c5932..5686606e 100644 --- a/Myriad/Extensions/CacheExtensions.cs +++ b/Myriad/Extensions/CacheExtensions.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using System.Threading.Tasks; using Myriad.Cache; +using Myriad.Rest; using Myriad.Types; namespace Myriad.Extensions @@ -41,5 +43,26 @@ namespace Myriad.Extensions throw new KeyNotFoundException($"User {roleId} not found in cache"); return role; } + + public static async ValueTask GetOrFetchUser(this IDiscordCache cache, DiscordApiClient rest, ulong userId) + { + if (cache.TryGetUser(userId, out var cacheUser)) + return cacheUser; + + var restUser = await rest.GetUser(userId); + if (restUser != null) + await cache.SaveUser(restUser); + return restUser; + } + + public static async Task GetOrCreateDmChannel(this IDiscordCache cache, DiscordApiClient rest, ulong recipientId) + { + if (cache.TryGetDmChannel(recipientId, out var cacheChannel)) + return cacheChannel; + + var restChannel = await rest.CreateDm(recipientId); + await cache.SaveChannel(restChannel); + return restChannel; + } } } \ No newline at end of file diff --git a/Myriad/Rest/DiscordApiClient.cs b/Myriad/Rest/DiscordApiClient.cs index 953ce2d0..11bdddb9 100644 --- a/Myriad/Rest/DiscordApiClient.cs +++ b/Myriad/Rest/DiscordApiClient.cs @@ -120,6 +120,9 @@ namespace Myriad.Rest _client.PostMultipart($"/webhooks/{webhookId}/{webhookToken}?wait=true", ("ExecuteWebhook", webhookId), request, files)!; + public Task CreateDm(ulong recipientId) => + _client.Post($"/users/@me/channels", ("CreateDM", default), new CreateDmRequest(recipientId))!; + private static string EncodeEmoji(Emoji emoji) => WebUtility.UrlEncode(emoji.Name) ?? emoji.Id?.ToString() ?? throw new ArgumentException("Could not encode emoji"); diff --git a/Myriad/Rest/Types/Requests/CreateDmRequest.cs b/Myriad/Rest/Types/Requests/CreateDmRequest.cs new file mode 100644 index 00000000..f28b2fe2 --- /dev/null +++ b/Myriad/Rest/Types/Requests/CreateDmRequest.cs @@ -0,0 +1,4 @@ +namespace Myriad.Rest.Types.Requests +{ + public record CreateDmRequest(ulong RecipientId); +} \ No newline at end of file diff --git a/Myriad/Types/Channel.cs b/Myriad/Types/Channel.cs index 2ac13cc6..841a2e1d 100644 --- a/Myriad/Types/Channel.cs +++ b/Myriad/Types/Channel.cs @@ -22,6 +22,7 @@ public bool? Nsfw { get; init; } public ulong? ParentId { get; init; } public Overwrite[]? PermissionOverwrites { get; init; } + public User[]? Recipients { get; init; } public record Overwrite { diff --git a/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs b/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs index cb915c99..136b75d2 100644 --- a/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs +++ b/PluralKit.Bot/CommandSystem/ContextEntityArgumentsExt.cs @@ -1,6 +1,7 @@ using System.Threading.Tasks; using Myriad.Cache; +using Myriad.Extensions; using Myriad.Types; using PluralKit.Bot.Utils; diff --git a/PluralKit.Bot/Commands/Token.cs b/PluralKit.Bot/Commands/Token.cs index 00bc8f63..2c34fe38 100644 --- a/PluralKit.Bot/Commands/Token.cs +++ b/PluralKit.Bot/Commands/Token.cs @@ -1,6 +1,9 @@ using System.Threading.Tasks; +using Myriad.Extensions; using Myriad.Rest.Exceptions; +using Myriad.Rest.Types.Requests; +using Myriad.Types; using PluralKit.Core; @@ -26,22 +29,22 @@ namespace PluralKit.Bot try { // DM the user a security disclaimer, and then the token in a separate message (for easy copying on mobile) - var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id); - await dm.SendMessageFixedAsync( - $"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:"); - await dm.SendMessageFixedAsync(token); + var dm = await ctx.Cache.GetOrCreateDmChannel(ctx.RestNew, ctx.AuthorNew.Id); + await ctx.RestNew.CreateMessage(dm.Id, new MessageRequest + { + Content = $"{Emojis.Warn} Please note that this grants access to modify (and delete!) all your system data, so keep it safe and secure. If it leaks or you need a new one, you can invalidate this one with `pk;token refresh`.\n\nYour token is below:" + }); + await ctx.RestNew.CreateMessage(dm.Id, new MessageRequest {Content = token}); // If we're not already in a DM, reply with a reminder to check - // TODO: DMs - // if (!(ctx.Channel is DiscordDmChannel)) - // await ctx.Reply($"{Emojis.Success} Check your DMs!"); + if (ctx.ChannelNew.Type != Channel.ChannelType.Dm) + await ctx.Reply($"{Emojis.Success} Check your DMs!"); } catch (UnauthorizedException) { // Can't check for permission errors beforehand, so have to handle here :/ - // TODO: DMs - // if (!(ctx.Channel is DiscordDmChannel)) - // await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?"); + if (ctx.ChannelNew.Type != Channel.ChannelType.Dm) + await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?"); } } @@ -66,25 +69,26 @@ namespace PluralKit.Bot try { // DM the user an invalidation disclaimer, and then the token in a separate message (for easy copying on mobile) - var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id); - await dm.SendMessageFixedAsync($"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:"); + var dm = await ctx.Cache.GetOrCreateDmChannel(ctx.RestNew, ctx.AuthorNew.Id); + await ctx.RestNew.CreateMessage(dm.Id, new MessageRequest + { + Content = $"{Emojis.Warn} Your previous API token has been invalidated. You will need to change it anywhere it's currently used.\n\nYour token is below:" + }); // Make the new token after sending the first DM; this ensures if we can't DM, we also don't end up // breaking their existing token as a side effect :) var token = await MakeAndSetNewToken(ctx.System); - await dm.SendMessageFixedAsync(token); + await ctx.RestNew.CreateMessage(dm.Id, new MessageRequest { Content = token }); // If we're not already in a DM, reply with a reminder to check - // TODO: DMs - // if (!(ctx.Channel is DiscordDmChannel)) - // await ctx.Reply($"{Emojis.Success} Check your DMs!"); + if (ctx.ChannelNew.Type != Channel.ChannelType.Dm) + await ctx.Reply($"{Emojis.Success} Check your DMs!"); } catch (UnauthorizedException) { // Can't check for permission errors beforehand, so have to handle here :/ - // TODO: DMs - // if (!(ctx.Channel is DiscordDmChannel)) - // await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?"); + if (ctx.ChannelNew.Type != Channel.ChannelType.Dm) + await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?"); } } }