private async Task ExecuteProxy(Message trigger, MessageContext ctx, AutoproxySettings autoproxySettings, ProxyMatch match, bool allowEveryone, bool allowEmbeds) { // Create reply embed var embeds = new List <Embed>(); var content = ""; if (trigger.Type == Message.MessageType.Reply && trigger.MessageReference?.ChannelId == trigger.ChannelId) { var repliedTo = trigger.ReferencedMessage.Value; if (repliedTo != null) { if (trigger.Mentions.Length > 0 && repliedTo.Author.Id == trigger.Mentions[0].Id && !(trigger.Content.Contains($"<@{repliedTo.Author.Id}>") || trigger.Content.Contains($"<@!{repliedTo.Author.Id}>"))) { content = $"*<@{repliedTo.Author.Id}>*\n"; } var(nickname, avatar) = await FetchReferencedMessageAuthorInfo(trigger, repliedTo); var embed = CreateReplyEmbed(match, trigger, repliedTo, nickname, avatar); if (embed != null) { embeds.Add(embed); } } // TODO: have a clean error for when message can't be fetched instead of just being silent } // Send the webhook content += match.ProxyContent; if (!allowEmbeds) { content = content.BreakLinkEmbeds(); } var messageChannel = await _cache.GetChannel(trigger.ChannelId); var rootChannel = await _cache.GetRootChannel(trigger.ChannelId); var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null; var guild = await _cache.GetGuild(trigger.GuildId.Value); var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest { GuildId = trigger.GuildId !.Value, ChannelId = rootChannel.Id, ThreadId = threadId, Name = await FixSameName(messageChannel.Id, ctx, match.Member), AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)), Content = content, Attachments = trigger.Attachments, FileSizeLimit = guild.FileSizeLimit(), Embeds = embeds.ToArray(), Stickers = trigger.StickerItems, AllowEveryone = allowEveryone });
public static async Task <PermissionSet> PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId, GuildMemberPartial?member, bool isWebhook = false) { if (!(await cache.TryGetChannel(channelId) is Channel channel)) { // todo: handle channel not found better return(PermissionSet.Dm); } if (channel.GuildId == null) { return(PermissionSet.Dm); } var rootChannel = await cache.GetRootChannel(channelId); var guild = await cache.GetGuild(channel.GuildId.Value); if (isWebhook) { return(EveryonePermissions(guild)); } return(PermissionsFor(guild, rootChannel, userId, member)); }
public async Task Handle(int shardId, MessageCreateEvent evt) { if (evt.Author.Id == await _cache.GetOwnUser()) { return; } if (evt.Type != Message.MessageType.Default && evt.Type != Message.MessageType.Reply) { return; } if (IsDuplicateMessage(evt)) { return; } if (!(await _cache.PermissionsIn(evt.ChannelId)).HasFlag(PermissionSet.SendMessages)) { return; } // spawn off saving the private channel into another thread // it is not a fatal error if this fails, and it shouldn't block message processing _ = _dmCache.TrySavePrivateChannel(evt); var guild = evt.GuildId != null ? await _cache.GetGuild(evt.GuildId.Value) : null; var channel = await _cache.GetChannel(evt.ChannelId); var rootChannel = await _cache.GetRootChannel(evt.ChannelId); // Log metrics and message info _metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived); _lastMessageCache.AddMessage(evt); // Get message context from DB (tracking w/ metrics) MessageContext ctx; using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime)) ctx = await _repo.GetMessageContext(evt.Author.Id, evt.GuildId ?? default, rootChannel.Id); // Try each handler until we find one that succeeds if (await TryHandleLogClean(evt, ctx)) { return; } // Only do command/proxy handling if it's a user account if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true) { return; } if (await TryHandleCommand(shardId, evt, guild, channel, ctx)) { return; } await TryHandleProxy(evt, guild, channel, ctx); }
public static PermissionSet PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId, ICollection <ulong>?userRoles) { var channel = cache.GetChannel(channelId); if (channel.GuildId == null) { return(PermissionSet.Dm); } var guild = cache.GetGuild(channel.GuildId.Value); return(PermissionsFor(guild, channel, userId, userRoles)); }
public async Task Handle(Shard shard, MessageCreateEvent evt) { if (evt.Author.Id == shard.User?.Id) { return; } if (evt.Type != Message.MessageType.Default && evt.Type != Message.MessageType.Reply) { return; } if (IsDuplicateMessage(evt)) { return; } var guild = evt.GuildId != null?_cache.GetGuild(evt.GuildId.Value) : null; var channel = _cache.GetChannel(evt.ChannelId); var rootChannel = _cache.GetRootChannel(evt.ChannelId); // Log metrics and message info _metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived); _lastMessageCache.AddMessage(evt); // Get message context from DB (tracking w/ metrics) MessageContext ctx; await using (var conn = await _db.Obtain()) using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime)) ctx = await _repo.GetMessageContext(conn, evt.Author.Id, evt.GuildId ?? default, rootChannel.Id); // Try each handler until we find one that succeeds if (await TryHandleLogClean(evt, ctx)) { return; } // Only do command/proxy handling if it's a user account if (evt.Author.Bot || evt.WebhookId != null || evt.Author.System == true) { return; } if (await TryHandleCommand(shard, evt, guild, channel, ctx)) { return; } await TryHandleProxy(shard, evt, guild, channel, ctx); }
public static PermissionSet PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId, ICollection <ulong>?userRoles, bool isWebhook = false) { if (!cache.TryGetChannel(channelId, out var channel)) { // todo: handle channel not found better return(PermissionSet.Dm); } if (channel.GuildId == null) { return(PermissionSet.Dm); } var rootChannel = cache.GetRootChannel(channelId); var guild = cache.GetGuild(channel.GuildId.Value); if (isWebhook) { return(EveryonePermissions(guild)); } return(PermissionsFor(guild, rootChannel, userId, userRoles)); }
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); }