diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 6a9694f8..d690817a 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -96,45 +96,69 @@ namespace PluralKit.Bot // Send the webhook var content = match.ProxyContent; if (!allowEmbeds) content = content.BreakLinkEmbeds(); - var id = await _webhookExecutor.ExecuteWebhook(trigger.Channel, match.Member.ProxyName(ctx), + var proxyMessage = await _webhookExecutor.ExecuteWebhook(trigger.Channel, match.Member.ProxyName(ctx), match.Member.ProxyAvatar(ctx), content, trigger.Attachments, allowEveryone); - Task SaveMessage() => _repo.AddMessage(conn, new PKMessage + await HandleProxyExecutedActions(conn, ctx, trigger, proxyMessage, match); + } + + private async Task HandleProxyExecutedActions(IPKConnection conn, MessageContext ctx, + DiscordMessage triggerMessage, DiscordMessage proxyMessage, + ProxyMatch match) + { + Task SaveMessageInDatabase() => _repo.AddMessage(conn, new PKMessage { - Channel = trigger.ChannelId, - Guild = trigger.Channel.GuildId, + Channel = triggerMessage.ChannelId, + Guild = triggerMessage.Channel.GuildId, Member = match.Member.Id, - Mid = id, - OriginalMid = trigger.Id, - Sender = trigger.Author.Id + Mid = proxyMessage.Id, + OriginalMid = triggerMessage.Id, + Sender = triggerMessage.Author.Id }); - Task LogMessage() => _logChannel.LogMessage(ctx, match, trigger, id).AsTask(); - async Task DeleteMessage() + Task LogMessageToChannel() => _logChannel.LogMessage(ctx, match, triggerMessage, proxyMessage.Id).AsTask(); + + async Task DeleteProxyTriggerMessage() { // Wait a second or so before deleting the original message await Task.Delay(MessageDeletionDelay); try { - await trigger.DeleteAsync(); + await triggerMessage.DeleteAsync(); } catch (NotFoundException) { - // If it's already deleted, we just log and swallow the exception - _logger.Warning("Attempted to delete already deleted proxy trigger message {Message}", trigger.Id); + _logger.Debug("Trigger message {TriggerMessageId} was already deleted when we attempted to; deleting proxy message {ProxyMessageId} also", + triggerMessage.Id, proxyMessage.Id); + await HandleTriggerAlreadyDeleted(proxyMessage); + // Swallow the exception, we don't need it } } // Run post-proxy actions (simultaneously; order doesn't matter) // Note that only AddMessage is using our passed-in connection, careful not to pass it elsewhere and run into conflicts await Task.WhenAll( - DeleteMessage(), - SaveMessage(), - LogMessage() + DeleteProxyTriggerMessage(), + SaveMessageInDatabase(), + LogMessageToChannel() ); } - + + private async Task HandleTriggerAlreadyDeleted(DiscordMessage proxyMessage) + { + // If a trigger message is deleted before we get to delete it, we can assume a mod bot or similar got to it + // In this case we should also delete the now-proxied message. + // This is going to hit the message delete event handler also, so that'll do the cleanup for us + + try + { + await proxyMessage.DeleteAsync(); + } + catch (NotFoundException) { } + catch (UnauthorizedException) { } + } + private async Task CheckBotPermissionsOrError(DiscordChannel channel) { var permissions = channel.BotPermissions(); diff --git a/PluralKit.Bot/Services/WebhookExecutorService.cs b/PluralKit.Bot/Services/WebhookExecutorService.cs index 76bd862f..d0917605 100644 --- a/PluralKit.Bot/Services/WebhookExecutorService.cs +++ b/PluralKit.Bot/Services/WebhookExecutorService.cs @@ -42,23 +42,23 @@ namespace PluralKit.Bot _logger = logger.ForContext(); } - public async Task ExecuteWebhook(DiscordChannel channel, string name, string avatarUrl, string content, IReadOnlyList attachments, bool allowEveryone) + public async Task ExecuteWebhook(DiscordChannel channel, string name, string avatarUrl, string content, IReadOnlyList attachments, bool allowEveryone) { _logger.Verbose("Invoking webhook in channel {Channel}", channel.Id); // Get a webhook, execute it var webhook = await _webhookCache.GetWebhook(channel); - var id = await ExecuteWebhookInner(channel, webhook, name, avatarUrl, content, attachments, allowEveryone); + var webhookMessage = await ExecuteWebhookInner(channel, webhook, name, avatarUrl, content, attachments, allowEveryone); // Log the relevant metrics _metrics.Measure.Meter.Mark(BotMetrics.MessagesProxied); _logger.Information("Invoked webhook {Webhook} in channel {Channel}", webhook.Id, channel.Id); - return id; + return webhookMessage; } - private async Task ExecuteWebhookInner(DiscordChannel channel, DiscordWebhook webhook, string name, string avatarUrl, string content, + private async Task ExecuteWebhookInner(DiscordChannel channel, DiscordWebhook webhook, string name, string avatarUrl, string content, IReadOnlyList attachments, bool allowEveryone, bool hasRetried = false) { content = content.Truncate(2000); @@ -77,11 +77,11 @@ namespace PluralKit.Bot await AddAttachmentsToBuilder(dwb, attachmentChunks[0]); } - DiscordMessage response; + DiscordMessage webhookMessage; using (_metrics.Measure.Timer.Time(BotMetrics.WebhookResponseTime)) { try { - response = await webhook.ExecuteAsync(dwb); + webhookMessage = await webhook.ExecuteAsync(dwb); } catch (JsonReaderException) { @@ -109,7 +109,7 @@ namespace PluralKit.Bot // We don't care about whether the sending succeeds, and we don't want to *wait* for it, so we just fork it off var _ = TrySendRemainingAttachments(webhook, name, avatarUrl, attachmentChunks); - return response.Id; + return webhookMessage; } private async Task TrySendRemainingAttachments(DiscordWebhook webhook, string name, string avatarUrl, IReadOnlyList> attachmentChunks)