diff --git a/PluralKit.Bot/Handlers/MessageDeleted.cs b/PluralKit.Bot/Handlers/MessageDeleted.cs index 9f1a607a..f0d5dd6d 100644 --- a/PluralKit.Bot/Handlers/MessageDeleted.cs +++ b/PluralKit.Bot/Handlers/MessageDeleted.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Myriad.Gateway; @@ -17,11 +18,13 @@ namespace PluralKit.Bot private readonly IDatabase _db; private readonly ModelRepository _repo; private readonly ILogger _logger; + private readonly LastMessageCacheService _lastMessage; - public MessageDeleted(ILogger logger, IDatabase db, ModelRepository repo) + public MessageDeleted(ILogger logger, IDatabase db, ModelRepository repo, LastMessageCacheService lastMessage) { _db = db; _repo = repo; + _lastMessage = lastMessage; _logger = logger.ForContext(); } @@ -36,6 +39,8 @@ namespace PluralKit.Bot await _db.Execute(c => _repo.DeleteMessage(c, evt.Id)); } + _lastMessage.HandleMessageDeletion(evt.ChannelId, evt.Id); + // Fork a task to delete the message after a short delay // to allow for lookups to happen for a little while after deletion _ = Inner(); @@ -54,6 +59,7 @@ namespace PluralKit.Bot await _db.Execute(c => _repo.DeleteMessagesBulk(c, evt.Ids)); } + _lastMessage.HandleMessageDeletion(evt.ChannelId, evt.Ids.ToList()); _ = Inner(); return Task.CompletedTask; } diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 3c2c9610..feda07a8 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -33,10 +33,11 @@ namespace PluralKit.Bot private readonly ProxyMatcher _matcher; private readonly IMetrics _metrics; private readonly IDiscordCache _cache; + private readonly LastMessageCacheService _lastMessage; private readonly DiscordApiClient _rest; - public ProxyService(LogChannelService logChannel, ILogger logger, - WebhookExecutorService webhookExecutor, IDatabase db, ProxyMatcher matcher, IMetrics metrics, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest) + public ProxyService(LogChannelService logChannel, ILogger logger, WebhookExecutorService webhookExecutor, IDatabase db, + ProxyMatcher matcher, IMetrics metrics, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest, LastMessageCacheService lastMessage) { _logChannel = logChannel; _webhookExecutor = webhookExecutor; @@ -45,6 +46,7 @@ namespace PluralKit.Bot _metrics = metrics; _repo = repo; _cache = cache; + _lastMessage = lastMessage; _rest = rest; _logger = logger.ForContext(); } @@ -142,7 +144,7 @@ namespace PluralKit.Bot GuildId = trigger.GuildId!.Value, ChannelId = rootChannel.Id, ThreadId = threadId, - Name = match.Member.ProxyName(ctx), + Name = await FixSameName(messageChannel.Id, ctx, match.Member), AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)), Content = content, Attachments = trigger.Attachments, @@ -230,6 +232,49 @@ namespace PluralKit.Bot }; } + private async Task FixSameName(ulong channel_id, MessageContext ctx, ProxyMember member) + { + var proxyName = member.ProxyName(ctx); + + Message? lastMessage = null; + + var lastMessageId = _lastMessage.GetLastMessage(channel_id)?.Previous; + if (lastMessageId == null) + // cache is out of date or channel is empty. + return proxyName; + + lastMessage = await _rest.GetMessage(channel_id, lastMessageId.Value); + + if (lastMessage == null) + // we don't have enough information to figure out if we need to fix the name, so bail here. + return proxyName; + + await using var conn = await _db.Obtain(); + var message = await _repo.GetMessage(conn, lastMessage.Id); + + if (lastMessage.Author.Username == proxyName) + { + // last message wasn't proxied by us, but somehow has the same name + // it's probably from a different webhook (Tupperbox?) but let's fix it anyway! + if (message == null) + return FixSameNameInner(proxyName); + + // last message was proxied by a different member + if (message.Member.Id != member.Id) + return FixSameNameInner(proxyName); + } + + // if we fixed the name last message and it's the same member proxying, we want to fix it again + if (lastMessage.Author.Username == FixSameNameInner(proxyName) && message?.Member.Id == member.Id) + return FixSameNameInner(proxyName); + + // No issues found, current proxy name is fine. + return proxyName; + } + + private string FixSameNameInner(string name) + => $"{name}\u17b5"; + private async Task HandleProxyExecutedActions(Shard shard, IPKConnection conn, MessageContext ctx, Message triggerMessage, Message proxyMessage, ProxyMatch match) diff --git a/PluralKit.Bot/Services/LastMessageCacheService.cs b/PluralKit.Bot/Services/LastMessageCacheService.cs index 0c77d6fc..f13d34e5 100644 --- a/PluralKit.Bot/Services/LastMessageCacheService.cs +++ b/PluralKit.Bot/Services/LastMessageCacheService.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using Myriad.Types; @@ -13,14 +14,58 @@ namespace PluralKit.Bot public void AddMessage(Message msg) { - _cache[msg.ChannelId] = new CachedMessage(msg.Id, msg.ReferencedMessage.Value?.Id); + var previous = GetLastMessage(msg.ChannelId); + _cache[msg.ChannelId] = new CachedMessage(msg.Id, msg.ReferencedMessage.Value?.Id, previous?.Id); } public CachedMessage? GetLastMessage(ulong channel) { return _cache.TryGetValue(channel, out var message) ? message : null; } + + public void HandleMessageDeletion(ulong channel, ulong message) + { + var storedMessage = GetLastMessage(channel); + if (storedMessage == null) + return; + + if (message == storedMessage.Id) + if (storedMessage.Previous != null) + _cache[channel] = new CachedMessage(storedMessage.Previous.Value, null, null); + else + _cache.Remove(channel); + else if (message == storedMessage.Previous) + _cache[channel] = new CachedMessage(storedMessage.Id, storedMessage.ReferencedMessage, null); + } + + public void HandleMessageDeletion(ulong channel, List messages) + { + var storedMessage = GetLastMessage(channel); + if (storedMessage == null) + return; + + if (!(messages.Contains(storedMessage.Id) || (storedMessage.Previous != null && messages.Contains(storedMessage.Previous.Value)))) + // none of the deleted messages are relevant to the cache + return; + + ulong? newLastMessage = null; + + if (messages.Contains(storedMessage.Id)) + newLastMessage = storedMessage.Previous; + + if (storedMessage.Previous != null && messages.Contains(storedMessage.Previous.Value)) + if (newLastMessage == storedMessage.Previous) + newLastMessage = null; + else + { + _cache[channel] = new CachedMessage(storedMessage.Id, storedMessage.ReferencedMessage, null); + return; + } + + if (newLastMessage == null) + _cache.Remove(channel); + } } - public record CachedMessage(ulong Id, ulong? ReferencedMessage); + public record CachedMessage(ulong Id, ulong? ReferencedMessage, ulong? Previous); }