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; 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) public static async Task<Channel> GetOrCreateDmChannel(this IDiscordCache cache, DiscordApiClient rest, ulong recipientId)
{ {
if (cache.TryGetDmChannel(recipientId, out var cacheChannel)) 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 record MessageUpdateEvent(ulong Id, ulong ChannelId): IGatewayEvent
{ {
public Optional<string?> Content { get; init; }
// TODO: lots of partials // TODO: lots of partials
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -101,18 +101,6 @@ namespace PluralKit.Bot
internal IDatabase Database => _db; internal IDatabase Database => _db;
internal ModelRepository Repository => _repo; 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) public async Task<Message> Reply(string text = null, Embed embed = null, AllowedMentions? mentions = null)
{ {
if (!BotPermissions.HasFlag(PermissionSet.SendMessages)) if (!BotPermissions.HasFlag(PermissionSet.SendMessages))

View File

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

View File

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

View File

@ -73,7 +73,7 @@ namespace PluralKit.Bot
public async Task ViewMember(Context ctx, PKMember target) public async Task ViewMember(Context ctx, PKMember target)
{ {
var system = await _db.Execute(c => _repo.GetSystem(c, target.System)); 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) 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)); var message = await _db.Execute(c => _repo.GetMessage(c, messageId));
if (message == null) throw Errors.MessageNotFound(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."); throw new PKError("Your system has no members! Please create at least one member before using this command.");
var randInt = randGen.Next(members.Count); 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) public async Task Group(Context ctx)
@ -73,7 +73,7 @@ namespace PluralKit.Bot
var ms = members.ToList(); var ms = members.ToList();
var randInt = randGen.Next(ms.Count); 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() async Task Inner()
{ {
await Task.Delay(MessageDeleteDelay); await Task.Delay(MessageDeleteDelay);
// TODO
await _db.Execute(c => _repo.DeleteMessage(c, evt.Id)); await _db.Execute(c => _repo.DeleteMessage(c, evt.Id));
} }

View File

@ -7,6 +7,7 @@ using Myriad.Gateway;
using Myriad.Rest; using Myriad.Rest;
using Myriad.Rest.Exceptions; using Myriad.Rest.Exceptions;
using Myriad.Rest.Types; using Myriad.Rest.Types;
using Myriad.Rest.Types.Requests;
using Myriad.Types; using Myriad.Types;
using PluralKit.Core; using PluralKit.Core;
@ -22,10 +23,11 @@ namespace PluralKit.Bot
private readonly CommandMessageService _commandMessageService; private readonly CommandMessageService _commandMessageService;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IDiscordCache _cache; private readonly IDiscordCache _cache;
private readonly EmbedService _embeds;
private readonly Bot _bot; private readonly Bot _bot;
private readonly DiscordApiClient _rest; 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; _db = db;
_repo = repo; _repo = repo;
@ -33,6 +35,7 @@ namespace PluralKit.Bot
_cache = cache; _cache = cache;
_bot = bot; _bot = bot;
_rest = rest; _rest = rest;
_embeds = embeds;
_logger = logger.ForContext<ReactionAdded>(); _logger = logger.ForContext<ReactionAdded>();
} }
@ -151,13 +154,22 @@ namespace PluralKit.Bot
private async ValueTask HandleQueryReaction(MessageReactionAddEvent evt, FullMessage msg) private async ValueTask HandleQueryReaction(MessageReactionAddEvent evt, FullMessage msg)
{ {
var guild = _cache.GetGuild(evt.GuildId!.Value);
// Try to DM the user info about the message // Try to DM the user info about the message
// var member = await evt.Guild.GetMember(evt.User.Id); // var member = await evt.Guild.GetMember(evt.User.Id);
try try
{ {
// TODO: how to DM? var dm = await _cache.GetOrCreateDmChannel(_rest, evt.UserId);
// await member.SendMessageAsync(embed: await _embeds.CreateMemberEmbed(msg.System, msg.Member, evt.Guild, LookupContext.ByNonOwner)); await _rest.CreateMessage(dm.Id, new MessageRequest
// await member.SendMessageAsync(embed: await _embeds.CreateMessageInfoEmbed(shard, msg)); {
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 :( 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) // If not, tell them in DMs (if we can)
try try
{ {
// todo: how to dm var dm = await _cache.GetOrCreateDmChannel(_rest, evt.UserId);
// 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 _rest.CreateMessage(dm.Id, new MessageRequest
// await guildUser.SendMessageFixedAsync($"<@{msg.Message.Sender}>".AsCode()); {
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) { } catch (UnauthorizedException) { }
} }

View File

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