diff --git a/PluralKit.API/Controllers/v2/DiscordControllerV2.cs b/PluralKit.API/Controllers/v2/DiscordControllerV2.cs index 26c03a3e..2d14c853 100644 --- a/PluralKit.API/Controllers/v2/DiscordControllerV2.cs +++ b/PluralKit.API/Controllers/v2/DiscordControllerV2.cs @@ -148,7 +148,7 @@ public class DiscordControllerV2: PKControllerBase if (msg == null) throw Errors.MessageNotFound; - var ctx = ContextFor(msg.System); + var ctx = msg.System == null ? LookupContext.ByNonOwner : ContextFor(msg.System); return msg.ToJson(ctx, APIVersion.V2); } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/Message.cs b/PluralKit.Bot/Commands/Message.cs index c698a74b..38a1d539 100644 --- a/PluralKit.Bot/Commands/Message.cs +++ b/PluralKit.Bot/Commands/Message.cs @@ -51,7 +51,7 @@ public class ProxiedMessage var msg = await GetMessageToEdit(ctx); - if (ctx.System.Id != msg.System.Id) + if (ctx.System.Id != msg.System?.Id) throw new PKError("Can't edit a message sent by a different system."); var newContent = ctx.RemainderOrNull().NormalizeLineEndSpacing(); @@ -209,7 +209,7 @@ public class ProxiedMessage if (!showContent) throw new PKError(noShowContentError); - if (message.System.Id != ctx.System.Id) + if (message.System?.Id != ctx.System.Id && message.Message.Sender != ctx.Author.Id) throw new PKError("You can only delete your own messages."); await ctx.Rest.DeleteMessage(message.Message.Channel, message.Message.Mid); diff --git a/PluralKit.Bot/Handlers/ReactionAdded.cs b/PluralKit.Bot/Handlers/ReactionAdded.cs index 0e8da24b..3c46e9c6 100644 --- a/PluralKit.Bot/Handlers/ReactionAdded.cs +++ b/PluralKit.Bot/Handlers/ReactionAdded.cs @@ -128,7 +128,7 @@ public class ReactionAdded: IEventHandler var system = await _repo.GetSystemByAccount(evt.UserId); // Can only delete your own message - if (msg.System.Id != system?.Id) return; + if (msg.System?.Id != system?.Id && msg.Message.Sender != evt.UserId) return; try { @@ -170,16 +170,17 @@ public class ReactionAdded: IEventHandler try { 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, - DateTimeZone.Utc - ) - }); + if (msg.Member != null) + await _rest.CreateMessage(dm.Id, new MessageRequest + { + Embed = await _embeds.CreateMemberEmbed( + msg.System, + msg.Member, + guild, + LookupContext.ByNonOwner, + DateTimeZone.Utc + ) + }); await _rest.CreateMessage( dm.Id, @@ -202,6 +203,8 @@ public class ReactionAdded: IEventHandler var requiredPerms = PermissionSet.ViewChannel | PermissionSet.SendMessages; if (member == null || !(await _cache.PermissionsFor(evt.ChannelId, member)).HasFlag(requiredPerms)) return; + if (msg.Member == null) return; + var config = await _repo.GetSystemConfig(msg.System.Id); if (config.PingsEnabled) diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index ec36b560..3bd8d159 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -355,13 +355,20 @@ public class EmbedService // Put it all together var eb = new EmbedBuilder() - .Author(new Embed.EmbedAuthor(msg.Member.NameFor(ctx), - IconUrl: msg.Member.AvatarFor(ctx).TryGetCleanCdnUrl())) + .Author(new Embed.EmbedAuthor(msg.Member?.NameFor(ctx) ?? "(deleted member)", + IconUrl: msg.Member?.AvatarFor(ctx).TryGetCleanCdnUrl())) .Description(content) .Image(showContent ? new Embed.EmbedImage(serverMsg?.Attachments?.FirstOrDefault()?.Url) : null) .Field(new Embed.Field("System", - msg.System.Name != null ? $"{msg.System.Name} (`{msg.System.Hid}`)" : $"`{msg.System.Hid}`", true)) - .Field(new Embed.Field("Member", $"{msg.Member.NameFor(ctx)} (`{msg.Member.Hid}`)", true)) + msg.System == null + ? "*(deleted or unknown system)*" + : msg.System.Name != null ? $"{msg.System.Name} (`{msg.System.Hid}`)" : $"`{msg.System.Hid}`" + , true)) + .Field(new Embed.Field("Member", + msg.Member == null + ? "*(deleted member)*" + : $"{msg.Member.NameFor(ctx)} (`{msg.Member.Hid}`)" + , true)) .Field(new Embed.Field("Sent by", userStr, true)) .Timestamp(DiscordUtils.SnowflakeToInstant(msg.Message.Mid).ToDateTimeOffset().ToString("O")); diff --git a/PluralKit.Bot/Services/LogChannelService.cs b/PluralKit.Bot/Services/LogChannelService.cs index df692557..748bee25 100644 --- a/PluralKit.Bot/Services/LogChannelService.cs +++ b/PluralKit.Bot/Services/LogChannelService.cs @@ -44,7 +44,7 @@ public class LogChannelService var triggerChannel = await _cache.GetChannel(proxiedMessage.Channel); var system = await _repo.GetSystem(ctx.SystemId.Value); - var member = await _repo.GetMember(proxiedMessage.Member); + var member = await _repo.GetMember(proxiedMessage.Member!.Value); // Send embed! var embed = _embed.CreateLoggedMessageEmbed(trigger, hookMessage, system.Hid, member, triggerChannel.Name, diff --git a/PluralKit.Core/Database/Migrations/24.sql b/PluralKit.Core/Database/Migrations/24.sql new file mode 100644 index 00000000..beba3a00 --- /dev/null +++ b/PluralKit.Core/Database/Migrations/24.sql @@ -0,0 +1,10 @@ +-- schema version 24 +-- don't drop message rows when system/member are deleted + +alter table messages alter column member drop not null; +alter table messages drop contstraint messages_member_fkey; +alter table messages + add constraint messages_member_fkey + foreign key (member) references members(id) on delete set null; + +update info set schema_version = 24; diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs index 932e44b6..9406b6ff 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs @@ -29,9 +29,13 @@ public partial class ModelRepository FullMessage Mapper(PKMessage msg, PKMember member, PKSystem system) => new() { Message = msg, System = system, Member = member }; + var query = "select * from messages" + + " left join members on messages.member = members.id" + + " left join systems on members.system = systems.id" + + " where (mid = @Id or original_mid = @Id)"; + var result = await conn.QueryAsync( - "select messages.*, members.*, systems.* from messages, members, systems where (mid = @Id or original_mid = @Id) and messages.member = members.id and systems.id = members.system", - Mapper, new { Id = id }); + query, Mapper, new { Id = id }); return result.FirstOrDefault(); } diff --git a/PluralKit.Core/Dispatch/DispatchService.cs b/PluralKit.Core/Dispatch/DispatchService.cs index 9e8eb04e..48cdfd3d 100644 --- a/PluralKit.Core/Dispatch/DispatchService.cs +++ b/PluralKit.Core/Dispatch/DispatchService.cs @@ -133,7 +133,7 @@ public class DispatchService if (system.WebhookUrl == null) return; - var member = await repo.GetMember(newMessage.Member); + var member = await repo.GetMember(newMessage.Member!.Value); var fullMessage = new FullMessage { diff --git a/PluralKit.Core/Models/PKMessage.cs b/PluralKit.Core/Models/PKMessage.cs index e4e2e9c0..6f6ca04c 100644 --- a/PluralKit.Core/Models/PKMessage.cs +++ b/PluralKit.Core/Models/PKMessage.cs @@ -9,7 +9,7 @@ public class PKMessage public ulong Mid { get; set; } public ulong? Guild { get; set; } // null value means "no data" (ie. from before this field being added) public ulong Channel { get; set; } - public MemberId Member { get; set; } + public MemberId? Member { get; set; } public ulong Sender { get; set; } public ulong? OriginalMid { get; set; } } @@ -17,8 +17,8 @@ public class PKMessage public class FullMessage { public PKMessage Message; - public PKMember Member; - public PKSystem System; + public PKMember? Member; + public PKSystem? System; public JObject ToJson(LookupContext ctx, APIVersion v) { @@ -30,8 +30,8 @@ public class FullMessage o.Add("sender", Message.Sender.ToString()); o.Add("channel", Message.Channel.ToString()); o.Add("guild", Message.Guild?.ToString()); - o.Add("system", System.ToJson(ctx, v)); - o.Add("member", Member.ToJson(ctx, v: v)); + o.Add("system", System?.ToJson(ctx, v)); + o.Add("member", Member?.ToJson(ctx, v: v)); return o; }