Port the DM stuff

This commit is contained in:
Ske 2020-12-25 13:58:45 +01:00
parent a2c8cbb560
commit 9079f1c59c
15 changed files with 95 additions and 77 deletions

View File

@ -55,6 +55,17 @@ namespace Myriad.Extensions
return restUser;
}
public static async ValueTask<Channel?> GetOrFetchChannel(this IDiscordCache cache, DiscordApiClient rest, ulong channelId)
{
if (cache.TryGetChannel(channelId, out var cacheChannel))
return cacheChannel;
var restChannel = await rest.GetChannel(channelId);
if (restChannel != null)
await cache.SaveChannel(restChannel);
return restChannel;
}
public static async Task<Channel> GetOrCreateDmChannel(this IDiscordCache cache, DiscordApiClient rest, ulong recipientId)
{
if (cache.TryGetDmChannel(recipientId, out var cacheChannel))

View File

@ -1,7 +1,10 @@
namespace Myriad.Gateway
using Myriad.Utils;
namespace Myriad.Gateway
{
public record MessageUpdateEvent(ulong Id, ulong ChannelId): IGatewayEvent
{
public Optional<string?> Content { get; init; }
// TODO: lots of partials
}
}

View File

@ -13,6 +13,7 @@ namespace Myriad.Serialization
opts.Converters.Add(new PermissionSetJsonConverter());
opts.Converters.Add(new ShardInfoJsonConverter());
opts.Converters.Add(new OptionalConverterFactory());
return opts;
}

View File

@ -7,26 +7,31 @@ using Myriad.Utils;
namespace Myriad.Serialization
{
public class OptionalConverter: JsonConverter<IOptional>
public class OptionalConverterFactory: JsonConverterFactory
{
public override IOptional? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
public class Inner<T>: JsonConverter<Optional<T>>
{
var innerType = typeToConvert.GetGenericArguments()[0];
var inner = JsonSerializer.Deserialize(ref reader, innerType, options);
public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var inner = JsonSerializer.Deserialize<T>(ref reader, options);
return new(inner!);
}
// TODO: rewrite to JsonConverterFactory to cut down on reflection
return (IOptional?) Activator.CreateInstance(
typeof(Optional<>).MakeGenericType(innerType),
BindingFlags.Instance | BindingFlags.Public,
null,
new[] {inner},
null);
public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value.HasValue ? value.GetValue() : default, typeof(T), options);
}
}
public override void Write(Utf8JsonWriter writer, IOptional value, JsonSerializerOptions options)
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
var innerType = value.GetType().GetGenericArguments()[0];
JsonSerializer.Serialize(writer, value.GetValue(), innerType, options);
var innerType = typeToConvert.GetGenericArguments()[0];
return (JsonConverter?) Activator.CreateInstance(
typeof(Inner<>).MakeGenericType(innerType),
BindingFlags.Instance | BindingFlags.Public,
null,
null,
null);
}
public override bool CanConvert(Type typeToConvert)

View File

@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
using System.Net.Mail;
using System.Text.Json.Serialization;
using Myriad.Utils;
namespace Myriad.Types
{
@ -59,8 +62,8 @@ namespace Myriad.Types
public Reference? MessageReference { get; set; }
public MessageFlags Flags { get; init; }
// todo: null vs. absence
public Message? ReferencedMessage { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<Message?> ReferencedMessage { get; init; }
public record Reference(ulong? GuildId, ulong? ChannelId, ulong? MessageId);

View File

@ -1,16 +1,10 @@
using System.Text.Json.Serialization;
using Myriad.Serialization;
namespace Myriad.Utils
namespace Myriad.Utils
{
public interface IOptional
{
bool HasValue { get; }
object? GetValue();
}
[JsonConverter(typeof(OptionalConverter))]
public readonly struct Optional<T>: IOptional
{
public Optional(T value)

View File

@ -101,18 +101,6 @@ namespace PluralKit.Bot
internal IDatabase Database => _db;
internal ModelRepository Repository => _repo;
public Task<DiscordMessage> Reply(string text, DiscordEmbed embed,
IEnumerable<IMention>? mentions = null)
{
throw new NotImplementedException();
}
public Task<DiscordMessage> Reply(DiscordEmbed embed,
IEnumerable<IMention>? mentions = null)
{
throw new NotImplementedException();
}
public async Task<Message> Reply(string text = null, Embed embed = null, AllowedMentions? mentions = null)
{
if (!BotPermissions.HasFlag(PermissionSet.SendMessages))

View File

@ -1,6 +1,5 @@
using System.Threading.Tasks;
using Myriad.Cache;
using Myriad.Extensions;
using Myriad.Types;

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using Myriad.Rest.Exceptions;
using Myriad.Types;
using Newtonsoft.Json;
@ -140,14 +141,14 @@ namespace PluralKit.Bot
try
{
var dm = await ctx.Rest.CreateDmAsync(ctx.Author.Id);
var dm = await ctx.Rest.CreateDmAsync(ctx.AuthorNew.Id);
// TODO: send file
var msg = await dm.SendFileAsync("system.json", stream, $"{Emojis.Success} Here you go!");
await dm.SendMessageAsync($"<{msg.Attachments[0].Url}>");
// If the original message wasn't posted in DMs, send a public reminder
// 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)
{

View File

@ -73,7 +73,7 @@ namespace PluralKit.Bot
public async Task ViewMember(Context ctx, PKMember target)
{
var system = await _db.Execute(c => _repo.GetSystem(c, target.System));
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target, ctx.Guild, ctx.LookupContextFor(system)));
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target, ctx.GuildNew, ctx.LookupContextFor(system)));
}
public async Task Soulscream(Context ctx, PKMember target)

View File

@ -228,7 +228,7 @@ namespace PluralKit.Bot {
var message = await _db.Execute(c => _repo.GetMessage(c, messageId));
if (message == null) throw Errors.MessageNotFound(messageId);
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(ctx.Shard, message));
await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message));
}
}
}

View File

@ -38,7 +38,7 @@ namespace PluralKit.Bot
throw new PKError("Your system has no members! Please create at least one member before using this command.");
var randInt = randGen.Next(members.Count);
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, members[randInt], ctx.Guild, ctx.LookupContextFor(ctx.System)));
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, members[randInt], ctx.GuildNew, ctx.LookupContextFor(ctx.System)));
}
public async Task Group(Context ctx)
@ -73,7 +73,7 @@ namespace PluralKit.Bot
var ms = members.ToList();
var randInt = randGen.Next(ms.Count);
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, ms[randInt], ctx.Guild, ctx.LookupContextFor(ctx.System)));
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, ms[randInt], ctx.GuildNew, ctx.LookupContextFor(ctx.System)));
}
}
}

View File

@ -33,7 +33,6 @@ namespace PluralKit.Bot
async Task Inner()
{
await Task.Delay(MessageDeleteDelay);
// TODO
await _db.Execute(c => _repo.DeleteMessage(c, evt.Id));
}

View File

@ -7,6 +7,7 @@ using Myriad.Gateway;
using Myriad.Rest;
using Myriad.Rest.Exceptions;
using Myriad.Rest.Types;
using Myriad.Rest.Types.Requests;
using Myriad.Types;
using PluralKit.Core;
@ -22,10 +23,11 @@ namespace PluralKit.Bot
private readonly CommandMessageService _commandMessageService;
private readonly ILogger _logger;
private readonly IDiscordCache _cache;
private readonly EmbedService _embeds;
private readonly Bot _bot;
private readonly DiscordApiClient _rest;
public ReactionAdded(ILogger logger, IDatabase db, ModelRepository repo, CommandMessageService commandMessageService, IDiscordCache cache, Bot bot, DiscordApiClient rest)
public ReactionAdded(ILogger logger, IDatabase db, ModelRepository repo, CommandMessageService commandMessageService, IDiscordCache cache, Bot bot, DiscordApiClient rest, EmbedService embeds)
{
_db = db;
_repo = repo;
@ -33,6 +35,7 @@ namespace PluralKit.Bot
_cache = cache;
_bot = bot;
_rest = rest;
_embeds = embeds;
_logger = logger.ForContext<ReactionAdded>();
}
@ -151,13 +154,22 @@ namespace PluralKit.Bot
private async ValueTask HandleQueryReaction(MessageReactionAddEvent evt, FullMessage msg)
{
var guild = _cache.GetGuild(evt.GuildId!.Value);
// Try to DM the user info about the message
// var member = await evt.Guild.GetMember(evt.User.Id);
try
{
// TODO: how to DM?
// await member.SendMessageAsync(embed: await _embeds.CreateMemberEmbed(msg.System, msg.Member, evt.Guild, LookupContext.ByNonOwner));
// await member.SendMessageAsync(embed: await _embeds.CreateMessageInfoEmbed(shard, msg));
var dm = await _cache.GetOrCreateDmChannel(_rest, evt.UserId);
await _rest.CreateMessage(dm.Id, new MessageRequest
{
Embed = await _embeds.CreateMemberEmbed(msg.System, msg.Member, guild, LookupContext.ByNonOwner)
});
await _rest.CreateMessage(dm.Id, new MessageRequest
{
Embed = await _embeds.CreateMessageInfoEmbed(msg)
});
}
catch (UnauthorizedException) { } // No permissions to DM, can't check for this :(
@ -192,9 +204,12 @@ namespace PluralKit.Bot
// If not, tell them in DMs (if we can)
try
{
// todo: how to dm
// await guildUser.SendMessageFixedAsync($"{Emojis.Error} {msg.Member.DisplayName()}'s system has disabled reaction pings. If you want to mention them anyway, you can copy/paste the following message:");
// await guildUser.SendMessageFixedAsync($"<@{msg.Message.Sender}>".AsCode());
var dm = await _cache.GetOrCreateDmChannel(_rest, evt.UserId);
await _rest.CreateMessage(dm.Id, new MessageRequest
{
Content = $"{Emojis.Error} {msg.Member.DisplayName()}'s system has disabled reaction pings. If you want to mention them anyway, you can copy/paste the following message:"
});
await _rest.CreateMessage(dm.Id, new MessageRequest {Content = $"<@{msg.Message.Sender}>".AsCode()});
}
catch (UnauthorizedException) { }
}

View File

@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DSharpPlus;
using DSharpPlus.Entities;
using Humanizer;
using Myriad.Builders;
using Myriad.Cache;
using Myriad.Extensions;
using Myriad.Rest;
using Myriad.Types;
@ -22,13 +22,11 @@ namespace PluralKit.Bot {
{
private readonly IDatabase _db;
private readonly ModelRepository _repo;
private readonly DiscordShardedClient _client;
private readonly IDiscordCache _cache;
private readonly DiscordApiClient _rest;
public EmbedService(DiscordShardedClient client, IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest)
public EmbedService(IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest)
{
_client = client;
_db = db;
_repo = repo;
_cache = cache;
@ -39,14 +37,7 @@ namespace PluralKit.Bot {
{
async Task<(ulong Id, User? User)> Inner(ulong id)
{
if (_cache.TryGetUser(id, out var cachedUser))
return (id, cachedUser);
var user = await _rest.GetUser(id);
if (user == null)
return (id, null);
// todo: move to "GetUserCached" helper
await _cache.SaveUser(user);
var user = await _cache.GetOrFetchUser(_rest, id);
return (id, user);
}
@ -108,7 +99,7 @@ namespace PluralKit.Bot {
.Build();
}
public async Task<Embed> CreateMemberEmbed(PKSystem system, PKMember member, DiscordGuild guild, LookupContext ctx)
public async Task<Embed> CreateMemberEmbed(PKSystem system, PKMember member, Guild guild, LookupContext ctx)
{
// string FormatTimestamp(Instant timestamp) => DateTimeFormats.ZonedDateTimeFormat.Format(timestamp.InZone(system.Zone));
@ -233,26 +224,33 @@ namespace PluralKit.Bot {
.Build();
}
public async Task<Embed> CreateMessageInfoEmbed(DiscordClient client, FullMessage msg)
public async Task<Embed> CreateMessageInfoEmbed(FullMessage msg)
{
var channel = await _cache.GetOrFetchChannel(_rest, msg.Message.Channel);
var ctx = LookupContext.ByNonOwner;
var channel = await _client.GetChannel(msg.Message.Channel);
var serverMsg = channel != null ? await channel.GetMessage(msg.Message.Mid) : null;
var serverMsg = channel != null ? await _rest.GetMessage(msg.Message.Channel, msg.Message.Mid) : null;
// Need this whole dance to handle cases where:
// - the user is deleted (userInfo == null)
// - the bot's no longer in the server we're querying (channel == null)
// - the member is no longer in the server we're querying (memberInfo == null)
DiscordMember memberInfo = null;
DiscordUser userInfo = null;
if (channel != null) memberInfo = await channel.Guild.GetMember(msg.Message.Sender);
if (memberInfo != null) userInfo = memberInfo; // Don't do an extra request if we already have this info from the member lookup
else userInfo = await client.GetUser(msg.Message.Sender);
// TODO: optimize ordering here a bit with new cache impl; and figure what happens if bot leaves server -> channel still cached -> hits this bit and 401s?
GuildMemberPartial memberInfo = null;
User userInfo = null;
if (channel != null)
{
var m = await _rest.GetGuildMember(channel.GuildId!.Value, msg.Message.Sender);
if (m != null)
// Don't do an extra request if we already have this info from the member lookup
userInfo = m.User;
memberInfo = m;
}
else userInfo = await _cache.GetOrFetchUser(_rest, msg.Message.Sender);
// Calculate string displayed under "Sent by"
string userStr;
if (memberInfo != null && memberInfo.Nickname != null)
userStr = $"**Username:** {memberInfo.NameAndMention()}\n**Nickname:** {memberInfo.Nickname}";
if (memberInfo != null && memberInfo.Nick != null)
userStr = $"**Username:** {userInfo.NameAndMention()}\n**Nickname:** {memberInfo.Nick}";
else if (userInfo != null) userStr = userInfo.NameAndMention();
else userStr = $"*(deleted user {msg.Message.Sender})*";
@ -270,7 +268,8 @@ namespace PluralKit.Bot {
var roles = memberInfo?.Roles?.ToList();
if (roles != null && roles.Count > 0)
{
var rolesString = string.Join(", ", roles.Select(role => role.Name));
// TODO: what if role isn't in cache? figure out a fallback
var rolesString = string.Join(", ", roles.Select(id => _cache.GetRole(id).Name));
eb.Field(new($"Account roles ({roles.Count})", rolesString.Truncate(1024)));
}