Beispiel #1
0
        public async ValueTask LogMessage(MessageContext ctx, ProxyMatch proxy, DiscordMessage trigger, ulong hookMessage)
        {
            if (ctx.SystemId == null || ctx.LogChannel == null || ctx.InLogBlacklist)
            {
                return;
            }

            // Find log channel and check if valid
            var logChannel = await FindLogChannel(trigger.Channel.GuildId, ctx.LogChannel.Value);

            if (logChannel == null || logChannel.Type != ChannelType.Text)
            {
                return;
            }

            // Check bot permissions
            if (!trigger.Channel.BotHasAllPermissions(Permissions.SendMessages | Permissions.EmbedLinks))
            {
                _logger.Information(
                    "Does not have permission to proxy log, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})",
                    ctx.LogChannel.Value, trigger.Channel.GuildId, trigger.Channel.BotPermissions());
                return;
            }

            // Send embed!
            await using var conn = await _db.Obtain();

            var embed = _embed.CreateLoggedMessageEmbed(await conn.QuerySystem(ctx.SystemId.Value),
                                                        await conn.QueryMember(proxy.Member.Id), hookMessage, trigger.Id, trigger.Author, proxy.Content,
                                                        trigger.Channel);
            var url = $"https://discord.com/channels/{trigger.Channel.GuildId}/{trigger.ChannelId}/{hookMessage}";
            await logChannel.SendMessageFixedAsync(content : url, embed : embed);
        }
        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);
        }
        private bool TryMatchAutoproxy(MessageContext ctx, IReadOnlyCollection <ProxyMember> members, string messageContent,
                                       out ProxyMatch match)
        {
            match = default;

            // Find the member we should autoproxy (null if none)
            var member = ctx.AutoproxyMode switch
            {
                AutoproxyMode.Member when ctx.AutoproxyMember != null =>
                members.FirstOrDefault(m => m.Id == ctx.AutoproxyMember),

                AutoproxyMode.Front when ctx.LastSwitchMembers.Length > 0 =>
                members.FirstOrDefault(m => m.Id == ctx.LastSwitchMembers[0]),

                AutoproxyMode.Latch when ctx.LastMessageMember != null && !IsLatchExpired(ctx.LastMessage) =>
                members.FirstOrDefault(m => m.Id == ctx.LastMessageMember.Value),

                _ => null
            };

            if (member == null)
            {
                return(false);
            }
            match = new ProxyMatch
            {
                Content = messageContent,
                Member  = member,

                // We're autoproxying, so not using any proxy tags here
                // we just find the first pair of tags (if any), otherwise null
                ProxyTags = member.ProxyTags.FirstOrDefault()
            };
            return(true);
        }
Beispiel #4
0
        public bool TryMatch(IEnumerable <ProxyMember> members, string?input, out ProxyMatch result)
        {
            result = default;

            // Null input is valid and is equivalent to empty string
            if (input == null)
            {
                return(false);
            }

            // If the message starts with a @mention, and then proceeds to have proxy tags,
            // extract the mention and place it inside the inner message
            // eg. @Ske [text] => [@Ske text]
            var leadingMention = ExtractLeadingMention(ref input);

            // "Flatten" list of members to a list of tag-member pairs
            // Then order them by "tag specificity"
            // (prefix+suffix length desc = inner message asc = more specific proxy first)
            var tags = members
                       .SelectMany(member => member.ProxyTags.Select(tag => (tag, member)))
                       .OrderByDescending(p => p.tag.ProxyString.Length);

            // Iterate now-ordered list of tags and try matching each one
            foreach (var(tag, member) in tags)
            {
                result.ProxyTags = tag;
                result.Member    = member;

                // Skip blank tags (shouldn't ever happen in practice)
                if (tag.Prefix == null && tag.Suffix == null)
                {
                    continue;
                }

                // Can we match with these tags?
                if (TryMatchTagsInner(input, tag, out result.Content))
                {
                    // If we extracted a leading mention before, add that back now
                    if (leadingMention != null)
                    {
                        result.Content = $"{leadingMention} {result.Content}";
                    }
                    return(true);
                }

                // (if not, keep going)
            }

            // We couldn't match anything :(
            return(false);
        }
Beispiel #5
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()
                );
        }
        private async Task ExecuteProxy(Shard shard, IPKConnection conn, Message trigger, MessageContext ctx,
                                        ProxyMatch match, bool allowEveryone, bool allowEmbeds)
        {
            // Create reply embed
            var embeds = new List <Embed>();

            if (trigger.Type == Message.MessageType.Reply && trigger.MessageReference?.ChannelId == trigger.ChannelId)
            {
                var repliedTo = trigger.ReferencedMessage.Value;
                if (repliedTo != null)
                {
                    var nickname = await FetchReferencedMessageAuthorNickname(trigger, repliedTo);

                    var embed = CreateReplyEmbed(match, trigger, repliedTo, nickname);
                    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 messageChannel = _cache.GetChannel(trigger.ChannelId);
            var rootChannel    = _cache.GetRootChannel(trigger.ChannelId);
            var threadId       = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null;

            var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
            {
                GuildId       = trigger.GuildId !.Value,
                ChannelId     = rootChannel.Id,
                ThreadId      = threadId,
                Name          = match.Member.ProxyName(ctx),
                AvatarUrl     = match.Member.ProxyAvatar(ctx),
                Content       = content,
                Attachments   = trigger.Attachments,
                Embeds        = embeds.ToArray(),
                AllowEveryone = allowEveryone,
            });
        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()
                );
        }
        public async ValueTask LogMessage(MessageContext ctx, ProxyMatch proxy, Message trigger, ulong hookMessage)
        {
            var logChannel = await GetAndCheckLogChannel(ctx, trigger);

            if (logChannel == null)
            {
                return;
            }

            var triggerChannel = _cache.GetChannel(trigger.ChannelId);

            // Send embed!
            await using var conn = await _db.Obtain();

            var embed = _embed.CreateLoggedMessageEmbed(await _repo.GetSystem(conn, ctx.SystemId.Value),
                                                        await _repo.GetMember(conn, proxy.Member.Id), hookMessage, trigger.Id, trigger.Author, proxy.Content,
                                                        triggerChannel);
            var url = $"https://discord.com/channels/{trigger.GuildId}/{trigger.ChannelId}/{hookMessage}";
            await _rest.CreateMessage(logChannel.Id, new() { Content = url, Embed = embed });
        }
Beispiel #9
0
        public async ValueTask LogMessage(MessageContext ctx, ProxyMatch proxy, Message trigger, ulong hookMessage)
        {
            if (ctx.SystemId == null || ctx.LogChannel == null || ctx.InLogBlacklist)
            {
                return;
            }

            // Find log channel and check if valid
            var logChannel = await FindLogChannel(trigger.GuildId !.Value, ctx.LogChannel.Value);

            if (logChannel == null || logChannel.Type != Channel.ChannelType.GuildText)
            {
                return;
            }

            var triggerChannel = _cache.GetChannel(trigger.ChannelId);

            // Check bot permissions
            var perms = _bot.PermissionsIn(logChannel.Id);

            if (!perms.HasFlag(PermissionSet.SendMessages | PermissionSet.EmbedLinks))
            {
                _logger.Information(
                    "Does not have permission to proxy log, ignoring (channel: {ChannelId}, guild: {GuildId}, bot permissions: {BotPermissions})",
                    ctx.LogChannel.Value, trigger.GuildId !.Value, perms);
                return;
            }

            // Send embed!
            await using var conn = await _db.Obtain();

            var embed = _embed.CreateLoggedMessageEmbed(await _repo.GetSystem(conn, ctx.SystemId.Value),
                                                        await _repo.GetMember(conn, proxy.Member.Id), hookMessage, trigger.Id, trigger.Author, proxy.Content,
                                                        triggerChannel);
            var url = $"https://discord.com/channels/{trigger.GuildId}/{trigger.ChannelId}/{hookMessage}";
            await _rest.CreateMessage(logChannel.Id, new() { Content = url, Embed = embed });
        }
        private bool TryMatchTags(IReadOnlyCollection <ProxyMember> members, string messageContent, bool hasAttachments, out ProxyMatch match)
        {
            if (!_parser.TryMatch(members, messageContent, out match))
            {
                return(false);
            }

            // Edge case: If we got a match with blank inner text, we'd normally just send w/ attachments
            // However, if there are no attachments, the user probably intended something else, so we "un-match" and proceed to autoproxy
            return(hasAttachments || match.Content.Trim().Length > 0);
        }
 public bool TryMatch(MessageContext ctx, IReadOnlyCollection <ProxyMember> members, out ProxyMatch match, string messageContent,
                      bool hasAttachments, bool allowAutoproxy)
 {
     if (TryMatchTags(members, messageContent, hasAttachments, out match))
     {
         return(true);
     }
     if (allowAutoproxy && TryMatchAutoproxy(ctx, members, messageContent, out match))
     {
         return(true);
     }
     return(false);
 }