Example #1
0
    private async ValueTask HandleProxyDeleteReaction(MessageReactionAddEvent evt, FullMessage msg)
    {
        if (!(await _cache.PermissionsIn(evt.ChannelId)).HasFlag(PermissionSet.ManageMessages))
        {
            return;
        }

        var system = await _repo.GetSystemByAccount(evt.UserId);

        // Can only delete your own message
        if (msg.System?.Id != system?.Id && msg.Message.Sender != evt.UserId)
        {
            return;
        }

        try
        {
            await _rest.DeleteMessage(evt.ChannelId, evt.MessageId);
        }
        catch (NotFoundException)
        {
            // Message was deleted by something/someone else before we got to it
        }

        await _repo.DeleteMessage(evt.MessageId);
    }
Example #2
0
    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);
    }
Example #3
0
    private async Task HandleError <T>(IEventHandler <T> handler, T evt, ILifetimeScope serviceScope,
                                       Exception exc)
        where T : IGatewayEvent
    {
        _metrics.Measure.Meter.Mark(BotMetrics.BotErrors, exc.GetType().FullName);

        var ourUserId = await _cache.GetOwnUser();

        // Make this beforehand so we can access the event ID for logging
        var sentryEvent = new SentryEvent(exc);

        // If the event is us responding to our own error messages, don't bother logging
        if (evt is MessageCreateEvent mc && mc.Author.Id == ourUserId)
        {
            return;
        }

        var shouldReport = exc.IsOurProblem();

        if (shouldReport)
        {
            // only log exceptions if they're our problem
            _logger.Error(exc, "Exception in event handler: {SentryEventId}", sentryEvent.EventId);

            // Report error to Sentry
            // This will just no-op if there's no URL set
            var sentryScope = serviceScope.Resolve <Scope>();

            // Add some specific info about Discord error responses, as a breadcrumb
            // TODO: headers to dict
            // if (exc is BadRequestException bre)
            //     sentryScope.AddBreadcrumb(bre.Response, "response.error", data: new Dictionary<string, string>(bre.Response.Headers));
            // if (exc is NotFoundException nfe)
            //     sentryScope.AddBreadcrumb(nfe.Response, "response.error", data: new Dictionary<string, string>(nfe.Response.Headers));
            // if (exc is UnauthorizedException ue)
            //     sentryScope.AddBreadcrumb(ue.Response, "response.error", data: new Dictionary<string, string>(ue.Response.Headers));

            SentrySdk.CaptureEvent(sentryEvent, sentryScope);

            // most of these errors aren't useful...
            if (_config.DisableErrorReporting)
            {
                return;
            }

            // Once we've sent it to Sentry, report it to the user (if we have permission to)
            var reportChannel = handler.ErrorChannelFor(evt, ourUserId);
            if (reportChannel == null)
            {
                return;
            }

            var botPerms = await _cache.PermissionsIn(reportChannel.Value);

            if (botPerms.HasFlag(PermissionSet.SendMessages | PermissionSet.EmbedLinks))
            {
                await _errorMessageService.SendErrorMessage(reportChannel.Value, sentryEvent.EventId.ToString());
            }
        }
    }
Example #4
0
    private async ValueTask <bool> TryHandleProxy(MessageCreateEvent evt, Guild guild, Channel channel,
                                                  MessageContext ctx)
    {
        var botPermissions = await _cache.PermissionsIn(channel.Id);

        try
        {
            return(await _proxy.HandleIncomingMessage(evt, ctx, guild, channel, ctx.AllowAutoproxy,
                                                      botPermissions));
        }

        // Catch any failed proxy checks so they get ignored in the global error handler
        catch (ProxyService.ProxyChecksFailedException) { }

        catch (PKError e)
        {
            // User-facing errors, print to the channel properly formatted
            if (botPermissions.HasFlag(PermissionSet.SendMessages))
            {
                await _rest.CreateMessage(evt.ChannelId,
                                          new MessageRequest { Content = $"{Emojis.Error} {e.Message}" });
            }
        }

        return(false);
    }
Example #5
0
    public async Task Handle(int shardId, MessageUpdateEvent evt)
    {
        if (evt.Author.Value?.Id == await _cache.GetOwnUser())
        {
            return;
        }

        // Edit message events sometimes arrive with missing data; double-check it's all there
        if (!evt.Content.HasValue || !evt.Author.HasValue || !evt.Member.HasValue)
        {
            return;
        }

        var channel = await _cache.GetChannel(evt.ChannelId);

        if (!DiscordUtils.IsValidGuildChannel(channel))
        {
            return;
        }
        var guild = await _cache.GetGuild(channel.GuildId !.Value);

        var lastMessage = _lastMessageCache.GetLastMessage(evt.ChannelId)?.Current;

        // Only react to the last message in the channel
        if (lastMessage?.Id != evt.Id)
        {
            return;
        }

        // Just run the normal message handling code, with a flag to disable autoproxying
        MessageContext ctx;

        using (_metrics.Measure.Timer.Time(BotMetrics.MessageContextQueryTime))
            ctx = await _repo.GetMessageContext(evt.Author.Value !.Id, channel.GuildId !.Value, evt.ChannelId);

        var equivalentEvt = await GetMessageCreateEvent(evt, lastMessage, channel);

        var botPermissions = await _cache.PermissionsIn(channel.Id);

        try
        {
            await _proxy.HandleIncomingMessage(equivalentEvt, ctx, allowAutoproxy : false, guild : guild,
                                               channel : channel, botPermissions : botPermissions);
        }
        // Catch any failed proxy checks so they get ignored in the global error handler
        catch (ProxyService.ProxyChecksFailedException) { }
    }
Example #6
0
    public async Task SetLogChannel(Context ctx)
    {
        await ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");

        var settings = await ctx.Repository.GetGuild(ctx.Guild.Id);

        if (await ctx.MatchClear("the server log channel"))
        {
            await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { LogChannel = null });

            await ctx.Reply($"{Emojis.Success} Proxy logging channel cleared.");

            return;
        }

        if (!ctx.HasNext())
        {
            if (settings.LogChannel == null)
            {
                await ctx.Reply("This server does not have a log channel set.");

                return;
            }

            await ctx.Reply($"This server's log channel is currently set to <#{settings.LogChannel}>.");

            return;
        }

        Channel channel       = null;
        var     channelString = ctx.PeekArgument();

        channel = await ctx.MatchChannel();

        if (channel == null || channel.GuildId != ctx.Guild.Id)
        {
            throw Errors.ChannelNotFound(channelString);
        }
        if (channel.Type != Channel.ChannelType.GuildText)
        {
            throw new PKError("PluralKit cannot log messages to this type of channel.");
        }

        var perms = await _cache.PermissionsIn(channel.Id);

        if (!perms.HasFlag(PermissionSet.SendMessages))
        {
            throw new PKError("PluralKit is missing **Send Messages** permissions in the new log channel.");
        }
        if (!perms.HasFlag(PermissionSet.EmbedLinks))
        {
            throw new PKError("PluralKit is missing **Embed Links** permissions in the new log channel.");
        }

        await ctx.Repository.UpdateGuild(ctx.Guild.Id, new GuildPatch { LogChannel = channel.Id });

        await ctx.Reply($"{Emojis.Success} Proxy logging channel set to <#{channel.Id}>.");
    }
Example #7
0
    public async Task <ILogEventEnricher> GetEnricher(int shardId, IGatewayEvent evt)
    {
        var props = new List <LogEventProperty> {
            new("ShardId", new ScalarValue(shardId))
        };

        if (_botConfig.Cluster != null)
        {
            props.Add(new LogEventProperty("ClusterId", new ScalarValue(_botConfig.Cluster.NodeName)));
        }

        var(guild, channel) = GetGuildChannelId(evt);
        var user    = GetUserId(evt);
        var message = GetMessageId(evt);

        if (guild != null)
        {
            props.Add(new LogEventProperty("GuildId", new ScalarValue(guild.Value)));
        }

        if (channel != null)
        {
            props.Add(new LogEventProperty("ChannelId", new ScalarValue(channel.Value)));

            if (await _cache.TryGetChannel(channel.Value) != null)
            {
                var botPermissions = await _cache.PermissionsIn(channel.Value);

                props.Add(new LogEventProperty("BotPermissions", new ScalarValue(botPermissions)));
            }
        }

        if (message != null)
        {
            props.Add(new LogEventProperty("MessageId", new ScalarValue(message.Value)));
        }

        if (user != null)
        {
            props.Add(new LogEventProperty("UserId", new ScalarValue(user.Value)));
        }

        if (evt is MessageCreateEvent mce)
        {
            props.Add(new LogEventProperty("UserPermissions", new ScalarValue(await _cache.PermissionsFor(mce))));
        }

        return(new Inner(props));
    }