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); }
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); }
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()); } } }
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); }
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) { } }
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}>."); }
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)); }