Example #1
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()
                );
        }
Example #2
0
        private async Task HandleProxyExecutedActions(DiscordClient shard, IPKConnection conn, MessageContext ctx,
                                                      DiscordMessage triggerMessage, DiscordMessage proxyMessage,
                                                      ProxyMatch match)
        {
            Task SaveMessageInDatabase() => _repo.AddMessage(conn, new PKMessage
            {
                Channel     = triggerMessage.ChannelId,
                Guild       = triggerMessage.Channel.GuildId,
                Member      = match.Member.Id,
                Mid         = proxyMessage.Id,
                OriginalMid = triggerMessage.Id,
                Sender      = triggerMessage.Author.Id
            });

            Task LogMessageToChannel() => _logChannel.LogMessage(shard, 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 triggerMessage.DeleteAsync();
                }
                catch (NotFoundException)
                {
                    _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(
                DeleteProxyTriggerMessage(),
                SaveMessageInDatabase(),
                LogMessageToChannel()
                );
        }
Example #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);
            }
        }