Port the DM stuff
This commit is contained in:
		| @@ -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)) | ||||
|   | ||||
| @@ -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 | ||||
|     } | ||||
| } | ||||
| @@ -13,6 +13,7 @@ namespace Myriad.Serialization | ||||
|  | ||||
|             opts.Converters.Add(new PermissionSetJsonConverter()); | ||||
|             opts.Converters.Add(new ShardInfoJsonConverter()); | ||||
|             opts.Converters.Add(new OptionalConverterFactory()); | ||||
|  | ||||
|             return opts; | ||||
|         } | ||||
|   | ||||
| @@ -7,28 +7,33 @@ 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>> | ||||
|         { | ||||
|             public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|             { | ||||
|                 var inner = JsonSerializer.Deserialize<T>(ref reader, options); | ||||
|                 return new(inner!); | ||||
|             } | ||||
|  | ||||
|             public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options) | ||||
|             { | ||||
|                 JsonSerializer.Serialize(writer, value.HasValue ? value.GetValue() : default, typeof(T), options); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) | ||||
|         { | ||||
|             var innerType = typeToConvert.GetGenericArguments()[0]; | ||||
|             var inner = JsonSerializer.Deserialize(ref reader, innerType, options); | ||||
|  | ||||
|             // TODO: rewrite to JsonConverterFactory to cut down on reflection | ||||
|             return (IOptional?) Activator.CreateInstance( | ||||
|                 typeof(Optional<>).MakeGenericType(innerType), | ||||
|             return (JsonConverter?) Activator.CreateInstance( | ||||
|                 typeof(Inner<>).MakeGenericType(innerType), | ||||
|                 BindingFlags.Instance | BindingFlags.Public, | ||||
|                 null, | ||||
|                 new[] {inner},  | ||||
|                 null, | ||||
|                 null); | ||||
|         } | ||||
|  | ||||
|         public override void Write(Utf8JsonWriter writer, IOptional value, JsonSerializerOptions options) | ||||
|         { | ||||
|             var innerType = value.GetType().GetGenericArguments()[0]; | ||||
|             JsonSerializer.Serialize(writer, value.GetValue(), innerType, options); | ||||
|         } | ||||
|  | ||||
|         public override bool CanConvert(Type typeToConvert) | ||||
|         { | ||||
|             if (!typeToConvert.IsGenericType) | ||||
|   | ||||
| @@ -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); | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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)) | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| using Myriad.Cache; | ||||
| using Myriad.Extensions; | ||||
| using Myriad.Types; | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|             { | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -33,7 +33,6 @@ namespace PluralKit.Bot | ||||
|             async Task Inner() | ||||
|             { | ||||
|                 await Task.Delay(MessageDeleteDelay); | ||||
|                 // TODO | ||||
|                 await _db.Execute(c => _repo.DeleteMessage(c, evt.Id)); | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -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) { } | ||||
|             } | ||||
|   | ||||
| @@ -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))); | ||||
|             } | ||||
|              | ||||
|   | ||||
		Reference in New Issue
	
	Block a user