Fix Discord merging webhook messages with same username

Closes #33.
This commit is contained in:
spiral 2021-08-03 13:44:22 -04:00
parent c691adf8c9
commit 25f96dd920
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
3 changed files with 102 additions and 6 deletions

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Myriad.Gateway; using Myriad.Gateway;
@ -17,11 +18,13 @@ namespace PluralKit.Bot
private readonly IDatabase _db; private readonly IDatabase _db;
private readonly ModelRepository _repo; private readonly ModelRepository _repo;
private readonly ILogger _logger; 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; _db = db;
_repo = repo; _repo = repo;
_lastMessage = lastMessage;
_logger = logger.ForContext<MessageDeleted>(); _logger = logger.ForContext<MessageDeleted>();
} }
@ -36,6 +39,8 @@ namespace PluralKit.Bot
await _db.Execute(c => _repo.DeleteMessage(c, evt.Id)); 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 // Fork a task to delete the message after a short delay
// to allow for lookups to happen for a little while after deletion // to allow for lookups to happen for a little while after deletion
_ = Inner(); _ = Inner();
@ -54,6 +59,7 @@ namespace PluralKit.Bot
await _db.Execute(c => _repo.DeleteMessagesBulk(c, evt.Ids)); await _db.Execute(c => _repo.DeleteMessagesBulk(c, evt.Ids));
} }
_lastMessage.HandleMessageDeletion(evt.ChannelId, evt.Ids.ToList());
_ = Inner(); _ = Inner();
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -33,10 +33,11 @@ namespace PluralKit.Bot
private readonly ProxyMatcher _matcher; private readonly ProxyMatcher _matcher;
private readonly IMetrics _metrics; private readonly IMetrics _metrics;
private readonly IDiscordCache _cache; private readonly IDiscordCache _cache;
private readonly LastMessageCacheService _lastMessage;
private readonly DiscordApiClient _rest; private readonly DiscordApiClient _rest;
public ProxyService(LogChannelService logChannel, ILogger logger, public ProxyService(LogChannelService logChannel, ILogger logger, WebhookExecutorService webhookExecutor, IDatabase db,
WebhookExecutorService webhookExecutor, IDatabase db, ProxyMatcher matcher, IMetrics metrics, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest) ProxyMatcher matcher, IMetrics metrics, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest, LastMessageCacheService lastMessage)
{ {
_logChannel = logChannel; _logChannel = logChannel;
_webhookExecutor = webhookExecutor; _webhookExecutor = webhookExecutor;
@ -45,6 +46,7 @@ namespace PluralKit.Bot
_metrics = metrics; _metrics = metrics;
_repo = repo; _repo = repo;
_cache = cache; _cache = cache;
_lastMessage = lastMessage;
_rest = rest; _rest = rest;
_logger = logger.ForContext<ProxyService>(); _logger = logger.ForContext<ProxyService>();
} }
@ -142,7 +144,7 @@ namespace PluralKit.Bot
GuildId = trigger.GuildId!.Value, GuildId = trigger.GuildId!.Value,
ChannelId = rootChannel.Id, ChannelId = rootChannel.Id,
ThreadId = threadId, ThreadId = threadId,
Name = match.Member.ProxyName(ctx), Name = await FixSameName(messageChannel.Id, ctx, match.Member),
AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)), AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)),
Content = content, Content = content,
Attachments = trigger.Attachments, Attachments = trigger.Attachments,
@ -230,6 +232,49 @@ namespace PluralKit.Bot
}; };
} }
private async Task<string> 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, private async Task HandleProxyExecutedActions(Shard shard, IPKConnection conn, MessageContext ctx,
Message triggerMessage, Message proxyMessage, Message triggerMessage, Message proxyMessage,
ProxyMatch match) ProxyMatch match)

View File

@ -1,6 +1,7 @@
#nullable enable #nullable enable
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Myriad.Types; using Myriad.Types;
@ -13,14 +14,58 @@ namespace PluralKit.Bot
public void AddMessage(Message msg) 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) public CachedMessage? GetLastMessage(ulong channel)
{ {
return _cache.TryGetValue(channel, out var message) ? message : null; 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<ulong> 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);
} }