private async Task <Message> ExecuteWebhookInner(Webhook webhook, ProxyRequest req, bool hasRetried = false) { var guild = await _cache.GetGuild(req.GuildId); var content = req.Content.Truncate(2000); var allowedMentions = content.ParseMentions(); if (!req.AllowEveryone) { allowedMentions = allowedMentions.RemoveUnmentionableRoles(guild) with { // also clear @everyones Parse = Array.Empty <AllowedMentions.ParseType>() } } ; var webhookReq = new ExecuteWebhookRequest { Username = FixProxyName(req.Name).Truncate(80), Content = content, AllowedMentions = allowedMentions, AvatarUrl = !string.IsNullOrWhiteSpace(req.AvatarUrl) ? req.AvatarUrl : null, Embeds = req.Embeds, Stickers = req.Stickers, }; MultipartFile[] files = null; var attachmentChunks = ChunkAttachmentsOrThrow(req.Attachments, req.FileSizeLimit); if (attachmentChunks.Count > 0) { _logger.Information( "Invoking webhook with {AttachmentCount} attachments totalling {AttachmentSize} MiB in {AttachmentChunks} chunks", req.Attachments.Length, req.Attachments.Select(a => a.Size).Sum() / 1024 / 1024, attachmentChunks.Count); files = await GetAttachmentFiles(attachmentChunks[0]); webhookReq.Attachments = files.Select(f => new Message.Attachment { Id = (ulong)Array.IndexOf(files, f), Description = f.Description, Filename = f.Filename }).ToArray(); } Message webhookMessage; using (_metrics.Measure.Timer.Time(BotMetrics.WebhookResponseTime)) { try { webhookMessage = await _rest.ExecuteWebhook(webhook.Id, webhook.Token, webhookReq, files, req.ThreadId); } catch (JsonReaderException) { // This happens sometimes when we hit a CloudFlare error (or similar) on Discord's end // Nothing we can do about this - happens sometimes under server load, so just drop the message and give up throw new WebhookExecutionErrorOnDiscordsEnd(); } catch (NotFoundException e) { if (e.ErrorCode == 10015 && !hasRetried) { // Error 10015 = "Unknown Webhook" - this likely means the webhook was deleted // but is still in our cache. Invalidate, refresh, try again _logger.Warning("Error invoking webhook {Webhook} in channel {Channel} (thread {ThreadId})", webhook.Id, webhook.ChannelId, req.ThreadId); var newWebhook = await _webhookCache.InvalidateAndRefreshWebhook(req.ChannelId, webhook); return(await ExecuteWebhookInner(newWebhook, req, true)); } throw; } } // 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, req.Name, req.AvatarUrl, attachmentChunks, req.ThreadId); return(webhookMessage); }