private async Task ExecuteProxy(DiscordClient shard, IPKConnection conn, DiscordMessage trigger, MessageContext ctx,
                                        ProxyMatch match, bool allowEveryone, bool allowEmbeds)
        {
            // Create reply embed
            var embeds = new List <DiscordEmbed>();

            if (trigger.Reference?.Channel?.Id == trigger.ChannelId)
            {
                var repliedTo = await FetchReplyOriginalMessage(trigger.Reference);

                if (repliedTo != null)
                {
                    var embed = CreateReplyEmbed(repliedTo);
                    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
            var content = match.ProxyContent;

            if (!allowEmbeds)
            {
                content = content.BreakLinkEmbeds();
            }
            var proxyMessage = await _webhookExecutor.ExecuteWebhook(trigger.Channel, FixSingleCharacterName(match.Member.ProxyName(ctx)),
                                                                     match.Member.ProxyAvatar(ctx),
                                                                     content, trigger.Attachments, embeds, allowEveryone);

            await HandleProxyExecutedActions(shard, conn, ctx, trigger, proxyMessage, match);
        }
Beispiel #2
0
        private async Task ExecuteProxy(IPKConnection conn, DiscordMessage trigger, MessageContext ctx,
                                        ProxyMatch match, bool allowEveryone, bool allowEmbeds)
        {
            // Send the webhook
            var content = match.ProxyContent;

            if (!allowEmbeds)
            {
                content = content.BreakLinkEmbeds();
            }
            var id = await _webhookExecutor.ExecuteWebhook(trigger.Channel, match.Member.ProxyName(ctx),
                                                           match.Member.ProxyAvatar(ctx),
                                                           content, trigger.Attachments, allowEveryone);

            Task SaveMessage() => _repo.AddMessage(conn, new PKMessage
            {
                Channel     = trigger.ChannelId,
                Guild       = trigger.Channel.GuildId,
                Member      = match.Member.Id,
                Mid         = id,
                OriginalMid = trigger.Id,
                Sender      = trigger.Author.Id
            });

            Task LogMessage() => _logChannel.LogMessage(ctx, match, trigger, id).AsTask();

            async Task DeleteMessage()
            {
                // Wait a second or so before deleting the original message
                await Task.Delay(MessageDeletionDelay);

                try
                {
                    await trigger.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);
                }
            }

            // 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()
                );
        }
Beispiel #3
0
        public async Task HandleMessageAsync(IMessage message)
        {
            // Bail early if this isn't in a guild channel
            if (!(message.Channel is ITextChannel channel))
            {
                return;
            }

            // Find a member with proxy tags matching the message
            var results = await _cache.GetResultsFor(message.Author.Id);

            var match = GetProxyTagMatch(message.Content, results);

            if (match == null)
            {
                return;
            }

            // And make sure the channel's not blacklisted from proxying.
            var guildCfg = await _data.GetOrCreateGuildConfig(channel.GuildId);

            if (guildCfg.Blacklist.Contains(channel.Id))
            {
                return;
            }

            // We know message.Channel can only be ITextChannel as PK doesn't work in DMs/groups
            // Afterwards we ensure the bot has the right permissions, otherwise bail early
            if (!await EnsureBotPermissions(channel))
            {
                return;
            }

            // Can't proxy a message with no content and no attachment
            if (match.InnerText.Trim().Length == 0 && message.Attachments.Count == 0)
            {
                return;
            }

            // Get variables in order and all
            var proxyName = match.Member.ProxyName(match.System.Tag);
            var avatarUrl = match.Member.AvatarUrl ?? match.System.AvatarUrl;

            // If the name's too long (or short), bail
            if (proxyName.Length < 2)
            {
                throw Errors.ProxyNameTooShort(proxyName);
            }
            if (proxyName.Length > Limits.MaxProxyNameLength)
            {
                throw Errors.ProxyNameTooLong(proxyName);
            }

            // Add the proxy tags into the proxied message if that option is enabled
            var messageContents = match.Member.KeepProxy
                ? $"{match.ProxyTags.Prefix}{match.InnerText}{match.ProxyTags.Suffix}"
                : match.InnerText;

            // Sanitize @everyone, but only if the original user wouldn't have permission to
            messageContents = SanitizeEveryoneMaybe(message, messageContents);

            // Execute the webhook itself
            var hookMessageId = await _webhookExecutor.ExecuteWebhook(
                channel,
                proxyName, avatarUrl,
                messageContents,
                message.Attachments.FirstOrDefault()
                );

            // Store the message in the database, and log it in the log channel (if applicable)
            await _data.AddMessage(message.Author.Id, hookMessageId, message.Channel.Id, message.Id, match.Member);

            await _logChannel.LogMessage(match.System, match.Member, hookMessageId, message.Id, message.Channel as IGuildChannel, message.Author, match.InnerText);

            // Wait a second or so before deleting the original message
            await Task.Delay(1000);

            try
            {
                await message.DeleteAsync();
            }
            catch (HttpException)
            {
                // If it's already deleted, we just log and swallow the exception
                _logger.Warning("Attempted to delete already deleted proxy trigger message {Message}", message.Id);
            }
        }
        public async Task HandleMessageAsync(GuildConfig guild, CachedAccount account, IMessage message, bool doAutoProxy)
        {
            // Bail early if this isn't in a guild channel
            if (!(message.Channel is ITextChannel channel))
            {
                return;
            }

            // Find a member with proxy tags matching the message
            var match = GetProxyTagMatch(message.Content, account.System, account.Members);

            // O(n) lookup since n is small (max ~100 in prod) and we're more constrained by memory (for a dictionary) here
            var systemSettingsForGuild = account.SettingsForGuild(channel.GuildId);

            // If we didn't get a match by proxy tags, try to get one by autoproxy
            // Also try if we *did* get a match, but there's no inner text. This happens if someone sends a message that
            // is equal to someone else's tags, and messages like these should be autoproxied if possible

            // All of this should only be done if this call allows autoproxy.
            // When a normal message is sent, autoproxy is enabled, but if this method is called from a message *edit*
            // event, then autoproxy is disabled. This is so AP doesn't "retrigger" when the original message was escaped.
            if (doAutoProxy && (match == null || (match.InnerText.Trim().Length == 0 && message.Attachments.Count == 0)))
            {
                match = await GetAutoproxyMatch(account, systemSettingsForGuild, message, channel);
            }

            // If we still haven't found any, just yeet
            if (match == null)
            {
                return;
            }

            // And make sure the channel's not blacklisted from proxying.
            if (guild.Blacklist.Contains(channel.Id))
            {
                return;
            }

            // Make sure the system hasn't blacklisted the guild either
            if (!systemSettingsForGuild.ProxyEnabled)
            {
                return;
            }

            // We know message.Channel can only be ITextChannel as PK doesn't work in DMs/groups
            // Afterwards we ensure the bot has the right permissions, otherwise bail early
            if (!await EnsureBotPermissions(channel))
            {
                return;
            }

            // Can't proxy a message with no content and no attachment
            if (match.InnerText.Trim().Length == 0 && message.Attachments.Count == 0)
            {
                return;
            }

            var memberSettingsForGuild = account.SettingsForMemberGuild(match.Member.Id, channel.GuildId);

            // Get variables in order and all
            var proxyName = match.Member.ProxyName(match.System.Tag, memberSettingsForGuild.DisplayName);
            var avatarUrl = memberSettingsForGuild.AvatarUrl ?? match.Member.AvatarUrl ?? match.System.AvatarUrl;

            // If the name's too long (or short), bail
            if (proxyName.Length < 2)
            {
                throw Errors.ProxyNameTooShort(proxyName);
            }
            if (proxyName.Length > Limits.MaxProxyNameLength)
            {
                throw Errors.ProxyNameTooLong(proxyName);
            }

            // Add the proxy tags into the proxied message if that option is enabled
            // Also check if the member has any proxy tags - some cases autoproxy can return a member with no tags
            var messageContents = (match.Member.KeepProxy && match.ProxyTags.HasValue)
                ? $"{match.ProxyTags.Value.Prefix}{match.InnerText}{match.ProxyTags.Value.Suffix}"
                : match.InnerText;

            // Sanitize @everyone, but only if the original user wouldn't have permission to
            messageContents = SanitizeEveryoneMaybe(message, messageContents);

            // Execute the webhook itself
            var hookMessageId = await _webhookExecutor.ExecuteWebhook(
                channel,
                proxyName, avatarUrl,
                messageContents,
                message.Attachments
                );

            // Store the message in the database, and log it in the log channel (if applicable)
            await _data.AddMessage(message.Author.Id, hookMessageId, channel.GuildId, message.Channel.Id, message.Id, match.Member);

            await _logChannel.LogMessage(match.System, match.Member, hookMessageId, message.Id, message.Channel as IGuildChannel, message.Author, match.InnerText, guild);

            // Wait a second or so before deleting the original message
            await Task.Delay(1000);

            try
            {
                await message.DeleteAsync();
            }
            catch (HttpException)
            {
                // If it's already deleted, we just log and swallow the exception
                _logger.Warning("Attempted to delete already deleted proxy trigger message {Message}", message.Id);
            }
        }