Add DM support

This commit is contained in:
Ske 2020-12-25 13:19:35 +01:00
parent 2e0c30eb5d
commit a2c8cbb560
9 changed files with 79 additions and 45 deletions

View File

@ -55,16 +55,5 @@ namespace Myriad.Cache
foreach (var mention in evt.Mentions) foreach (var mention in evt.Mentions)
await cache.SaveUser(mention); await cache.SaveUser(mention);
} }
public static async ValueTask<User?> 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;
}
} }
} }

View File

@ -19,6 +19,7 @@ namespace Myriad.Cache
public bool TryGetGuild(ulong guildId, out Guild guild); public bool TryGetGuild(ulong guildId, out Guild guild);
public bool TryGetChannel(ulong channelId, out Channel channel); 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 TryGetUser(ulong userId, out User user);
public bool TryGetRole(ulong roleId, out Role role); public bool TryGetRole(ulong roleId, out Role role);

View File

@ -10,18 +10,11 @@ namespace Myriad.Cache
{ {
public class MemoryDiscordCache: IDiscordCache public class MemoryDiscordCache: IDiscordCache
{ {
private readonly ConcurrentDictionary<ulong, Channel> _channels; private readonly ConcurrentDictionary<ulong, Channel> _channels = new();
private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds; private readonly ConcurrentDictionary<ulong, ulong> _dmChannels = new();
private readonly ConcurrentDictionary<ulong, Role> _roles; private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds = new();
private readonly ConcurrentDictionary<ulong, User> _users; private readonly ConcurrentDictionary<ulong, Role> _roles = new();
private readonly ConcurrentDictionary<ulong, User> _users = new();
public MemoryDiscordCache()
{
_guilds = new ConcurrentDictionary<ulong, CachedGuild>();
_channels = new ConcurrentDictionary<ulong, Channel>();
_users = new ConcurrentDictionary<ulong, User>();
_roles = new ConcurrentDictionary<ulong, Role>();
}
public ValueTask SaveGuild(Guild guild) public ValueTask SaveGuild(Guild guild)
{ {
@ -35,14 +28,21 @@ namespace Myriad.Cache
return default; return default;
} }
public ValueTask SaveChannel(Channel channel) public async ValueTask SaveChannel(Channel channel)
{ {
_channels[channel.Id] = channel; _channels[channel.Id] = channel;
if (channel.GuildId != null && _guilds.TryGetValue(channel.GuildId.Value, out var guild)) if (channel.GuildId != null && _guilds.TryGetValue(channel.GuildId.Value, out var guild))
guild.Channels.TryAdd(channel.Id, true); 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) public ValueTask SaveUser(User user)
@ -125,6 +125,14 @@ namespace Myriad.Cache
public bool TryGetChannel(ulong channelId, out Channel channel) => public bool TryGetChannel(ulong channelId, out Channel channel) =>
_channels.TryGetValue(channelId, out 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) => public bool TryGetUser(ulong userId, out User user) =>
_users.TryGetValue(userId, out user!); _users.TryGetValue(userId, out user!);

View File

@ -1,6 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using Myriad.Cache; using Myriad.Cache;
using Myriad.Rest;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions
@ -41,5 +43,26 @@ namespace Myriad.Extensions
throw new KeyNotFoundException($"User {roleId} not found in cache"); throw new KeyNotFoundException($"User {roleId} not found in cache");
return role; return role;
} }
public static async ValueTask<User?> 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<Channel> 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;
}
} }
} }

View File

@ -120,6 +120,9 @@ namespace Myriad.Rest
_client.PostMultipart<Message>($"/webhooks/{webhookId}/{webhookToken}?wait=true", _client.PostMultipart<Message>($"/webhooks/{webhookId}/{webhookToken}?wait=true",
("ExecuteWebhook", webhookId), request, files)!; ("ExecuteWebhook", webhookId), request, files)!;
public Task<Channel> CreateDm(ulong recipientId) =>
_client.Post<Channel>($"/users/@me/channels", ("CreateDM", default), new CreateDmRequest(recipientId))!;
private static string EncodeEmoji(Emoji emoji) => private static string EncodeEmoji(Emoji emoji) =>
WebUtility.UrlEncode(emoji.Name) ?? emoji.Id?.ToString() ?? WebUtility.UrlEncode(emoji.Name) ?? emoji.Id?.ToString() ??
throw new ArgumentException("Could not encode emoji"); throw new ArgumentException("Could not encode emoji");

View File

@ -0,0 +1,4 @@
namespace Myriad.Rest.Types.Requests
{
public record CreateDmRequest(ulong RecipientId);
}

View File

@ -22,6 +22,7 @@
public bool? Nsfw { get; init; } public bool? Nsfw { get; init; }
public ulong? ParentId { get; init; } public ulong? ParentId { get; init; }
public Overwrite[]? PermissionOverwrites { get; init; } public Overwrite[]? PermissionOverwrites { get; init; }
public User[]? Recipients { get; init; }
public record Overwrite public record Overwrite
{ {

View File

@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Myriad.Cache; using Myriad.Cache;
using Myriad.Extensions;
using Myriad.Types; using Myriad.Types;
using PluralKit.Bot.Utils; using PluralKit.Bot.Utils;

View File

@ -1,6 +1,9 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Myriad.Extensions;
using Myriad.Rest.Exceptions; using Myriad.Rest.Exceptions;
using Myriad.Rest.Types.Requests;
using Myriad.Types;
using PluralKit.Core; using PluralKit.Core;
@ -26,22 +29,22 @@ namespace PluralKit.Bot
try try
{ {
// DM the user a security disclaimer, and then the token in a separate message (for easy copying on mobile) // 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); var dm = await ctx.Cache.GetOrCreateDmChannel(ctx.RestNew, ctx.AuthorNew.Id);
await dm.SendMessageFixedAsync( await ctx.RestNew.CreateMessage(dm.Id, new MessageRequest
$"{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); 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 // If we're not already in a DM, reply with a reminder to check
// TODO: DMs if (ctx.ChannelNew.Type != Channel.ChannelType.Dm)
// if (!(ctx.Channel is DiscordDmChannel)) await ctx.Reply($"{Emojis.Success} Check your DMs!");
// await ctx.Reply($"{Emojis.Success} Check your DMs!");
} }
catch (UnauthorizedException) catch (UnauthorizedException)
{ {
// Can't check for permission errors beforehand, so have to handle here :/ // Can't check for permission errors beforehand, so have to handle here :/
// TODO: DMs if (ctx.ChannelNew.Type != Channel.ChannelType.Dm)
// if (!(ctx.Channel is DiscordDmChannel)) await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?");
// await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?");
} }
} }
@ -66,25 +69,26 @@ namespace PluralKit.Bot
try { try {
// DM the user an invalidation disclaimer, and then the token in a separate message (for easy copying on mobile) // 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); var dm = await ctx.Cache.GetOrCreateDmChannel(ctx.RestNew, ctx.AuthorNew.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:"); 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 // 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 :) // breaking their existing token as a side effect :)
var token = await MakeAndSetNewToken(ctx.System); 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 // If we're not already in a DM, reply with a reminder to check
// TODO: DMs if (ctx.ChannelNew.Type != Channel.ChannelType.Dm)
// if (!(ctx.Channel is DiscordDmChannel)) await ctx.Reply($"{Emojis.Success} Check your DMs!");
// await ctx.Reply($"{Emojis.Success} Check your DMs!");
} }
catch (UnauthorizedException) catch (UnauthorizedException)
{ {
// Can't check for permission errors beforehand, so have to handle here :/ // Can't check for permission errors beforehand, so have to handle here :/
// TODO: DMs if (ctx.ChannelNew.Type != Channel.ChannelType.Dm)
// if (!(ctx.Channel is DiscordDmChannel)) await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?");
// await ctx.Reply($"{Emojis.Error} Could not send token in DMs. Are your DMs closed?");
} }
} }
} }