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)
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 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);

View File

@ -10,19 +10,12 @@ namespace Myriad.Cache
{
public class MemoryDiscordCache: IDiscordCache
{
private readonly ConcurrentDictionary<ulong, Channel> _channels;
private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds;
private readonly ConcurrentDictionary<ulong, Role> _roles;
private readonly ConcurrentDictionary<ulong, User> _users;
public MemoryDiscordCache()
{
_guilds = new ConcurrentDictionary<ulong, CachedGuild>();
_channels = new ConcurrentDictionary<ulong, Channel>();
_users = new ConcurrentDictionary<ulong, User>();
_roles = new ConcurrentDictionary<ulong, Role>();
}
private readonly ConcurrentDictionary<ulong, Channel> _channels = new();
private readonly ConcurrentDictionary<ulong, ulong> _dmChannels = new();
private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds = new();
private readonly ConcurrentDictionary<ulong, Role> _roles = new();
private readonly ConcurrentDictionary<ulong, User> _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!);

View File

@ -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<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",
("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) =>
WebUtility.UrlEncode(emoji.Name) ?? emoji.Id?.ToString() ??
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 ulong? ParentId { get; init; }
public Overwrite[]? PermissionOverwrites { get; init; }
public User[]? Recipients { get; init; }
public record Overwrite
{

View File

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

View File

@ -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?");
}
}
}