public async Task EditMessage(Context ctx) { var msg = await GetMessageToEdit(ctx); if (ctx.System.Id != msg.System?.Id) { throw new PKError("Can't edit a message sent by a different system."); } if (!ctx.HasNext()) { throw new PKSyntaxError("You need to include the message to edit in."); } var newContent = ctx.RemainderOrNull().NormalizeLineEndSpacing(); if (newContent.Length > 2000) { throw new PKError("PluralKit cannot proxy messages over 2000 characters in length."); } var originalMsg = await _rest.GetMessageOrNull(msg.Message.Channel, msg.Message.Mid); if (originalMsg == null) { throw new PKError("Could not edit message."); } try { var editedMsg = await _webhookExecutor.EditWebhookMessage(msg.Message.Channel, msg.Message.Mid, newContent); if (ctx.Guild == null) { await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new Emoji { Name = Emojis.Success }); } if ((await ctx.BotPermissions).HasFlag(PermissionSet.ManageMessages)) { await _rest.DeleteMessage(ctx.Channel.Id, ctx.Message.Id); } await _logChannel.LogMessage(ctx.MessageContext, msg.Message, ctx.Message, editedMsg, originalMsg !.Content !); } catch (NotFoundException) { throw new PKError("Could not edit message."); } }
public async Task <Embed> CreateMessageInfoEmbed(FullMessage msg, bool showContent) { var channel = await _cache.GetOrFetchChannel(_rest, msg.Message.Channel); var ctx = LookupContext.ByNonOwner; var serverMsg = await _rest.GetMessageOrNull(msg.Message.Channel, msg.Message.Mid); // Need this whole dance to handle cases where: // - the user is deleted (userInfo == null) // - the bot's no longer in the server we're querying (channel == null) // - the member is no longer in the server we're querying (memberInfo == null) // TODO: optimize ordering here a bit with new cache impl; and figure what happens if bot leaves server -> channel still cached -> hits this bit and 401s? GuildMemberPartial memberInfo = null; User userInfo = null; if (channel != null) { GuildMember member = null; try { member = await _rest.GetGuildMember(channel.GuildId !.Value, msg.Message.Sender); } catch (ForbiddenException) { // no permission, couldn't fetch, oh well } if (member != null) { // Don't do an extra request if we already have this info from the member lookup userInfo = member.User; } memberInfo = member; } if (userInfo == null) { userInfo = await _cache.GetOrFetchUser(_rest, msg.Message.Sender); } // Calculate string displayed under "Sent by" string userStr; if (showContent && memberInfo != null && memberInfo.Nick != null) { userStr = $"**Username:** {userInfo.NameAndMention()}\n**Nickname:** {memberInfo.Nick}"; } else if (userInfo != null) { userStr = userInfo.NameAndMention(); } else { userStr = $"*(deleted user {msg.Message.Sender})*"; } var content = serverMsg?.Content?.NormalizeLineEndSpacing(); if (content == null || !showContent) { content = "*(message contents deleted or inaccessible)*"; } // Put it all together var eb = new EmbedBuilder() .Author(new Embed.EmbedAuthor(msg.Member?.NameFor(ctx) ?? "(deleted member)", IconUrl: msg.Member?.AvatarFor(ctx).TryGetCleanCdnUrl())) .Description(content) .Image(showContent ? new Embed.EmbedImage(serverMsg?.Attachments?.FirstOrDefault()?.Url) : null) .Field(new Embed.Field("System", msg.System == null ? "*(deleted or unknown system)*" : msg.System.Name != null ? $"{msg.System.Name} (`{msg.System.Hid}`)" : $"`{msg.System.Hid}`" , true)) .Field(new Embed.Field("Member", msg.Member == null ? "*(deleted member)*" : $"{msg.Member.NameFor(ctx)} (`{msg.Member.Hid}`)" , true)) .Field(new Embed.Field("Sent by", userStr, true)) .Timestamp(DiscordUtils.SnowflakeToInstant(msg.Message.Mid).ToDateTimeOffset().ToString("O")); var roles = memberInfo?.Roles?.ToList(); if (roles != null && roles.Count > 0 && showContent) { var rolesString = string.Join(", ", (await Task.WhenAll(roles .Select(async id => { var role = await _cache.TryGetRole(id); if (role != null) { return(role); } return(new Role { Name = "*(unknown role)*", Position = 0 }); }))) .OrderByDescending(role => role.Position) .Select(role => role.Name)); eb.Field(new Embed.Field($"Account roles ({roles.Count})", rolesString.Truncate(1024))); } return(eb.Build()); }
public async Task EditMessage(Context ctx) { var msg = await GetMessageToEdit(ctx, EditTimeout, false); if (ctx.System.Id != msg.System?.Id) { throw new PKError("Can't edit a message sent by a different system."); } if (!ctx.HasNext()) { throw new PKSyntaxError("You need to include the message to edit in."); } var originalMsg = await _rest.GetMessageOrNull(msg.Message.Channel, msg.Message.Mid); if (originalMsg == null) { throw new PKError("Could not edit message."); } // Check if we should append or prepend var append = ctx.MatchFlag("append"); var prepend = ctx.MatchFlag("prepend"); // Grab the original message content and new message content var originalContent = originalMsg.Content; var newContent = ctx.RemainderOrNull().NormalizeLineEndSpacing(); // Append or prepend the new content to the original message content if needed. // If no flag is supplied, the new contents will completly overwrite the old contents // If both flags are specified. the message will be prepended AND appended if (append && prepend) { newContent = $"{newContent} {originalContent} {newContent}"; } else if (append) { newContent = originalContent + " " + newContent; } else if (prepend) { newContent = newContent + " " + originalContent; } if (newContent.Length > 2000) { throw new PKError("PluralKit cannot proxy messages over 2000 characters in length."); } try { var editedMsg = await _webhookExecutor.EditWebhookMessage(msg.Message.Channel, msg.Message.Mid, newContent); if (ctx.Guild == null) { await _rest.CreateReaction(ctx.Channel.Id, ctx.Message.Id, new Emoji { Name = Emojis.Success }); } if ((await ctx.BotPermissions).HasFlag(PermissionSet.ManageMessages)) { await _rest.DeleteMessage(ctx.Channel.Id, ctx.Message.Id); } await _logChannel.LogMessage(ctx.MessageContext, msg.Message, ctx.Message, editedMsg, originalMsg !.Content !); } catch (NotFoundException) { throw new PKError("Could not edit message."); } }
public async Task MessageProxyCheck(Context ctx) { if (!ctx.HasNext() && ctx.Message.MessageReference == null) { throw new PKSyntaxError("You need to specify a message."); } var failedToGetMessage = "Could not find a valid message to check, was not able to fetch the message, or the message was not sent by you."; var(messageId, channelId) = ctx.MatchMessage(false); if (messageId == null || channelId == null) { throw new PKError(failedToGetMessage); } var proxiedMsg = await ctx.Database.Execute(conn => ctx.Repository.GetMessage(conn, messageId.Value)); if (proxiedMsg != null) { await ctx.Reply($"{Emojis.Success} This message was proxied successfully."); return; } // get the message info var msg = await _rest.GetMessageOrNull(channelId.Value, messageId.Value); if (msg == null) { throw new PKError(failedToGetMessage); } // if user is fetching a message in a different channel sent by someone else, throw a generic error message if (msg == null || msg.Author.Id != ctx.Author.Id && msg.ChannelId != ctx.Channel.Id) { throw new PKError(failedToGetMessage); } if ((_botConfig.Prefixes ?? BotConfig.DefaultPrefixes).Any(p => msg.Content.StartsWith(p))) { await ctx.Reply("This message starts with the bot's prefix, and was parsed as a command."); return; } if (msg.Author.Bot) { throw new PKError("You cannot check messages sent by a bot."); } if (msg.WebhookId != null) { throw new PKError("You cannot check messages sent by a webhook."); } if (msg.Author.Id != ctx.Author.Id && !ctx.CheckBotAdmin()) { throw new PKError("You can only check your own messages."); } // get the channel info var channel = await _rest.GetChannelOrNull(channelId.Value); if (channel == null) { throw new PKError("Unable to get the channel associated with this message."); } // using channel.GuildId here since _rest.GetMessage() doesn't return the GuildId var context = await ctx.Repository.GetMessageContext(msg.Author.Id, channel.GuildId.Value, msg.ChannelId); var members = (await ctx.Repository.GetProxyMembers(msg.Author.Id, channel.GuildId.Value)).ToList(); // for now this is just server var autoproxySettings = await ctx.Repository.GetAutoproxySettings(ctx.System.Id, channel.GuildId.Value, null); // todo: match unlatch // Run everything through the checks, catch the ProxyCheckFailedException, and reply with the error message. try { _proxy.ShouldProxy(channel, msg, context); _matcher.TryMatch(context, autoproxySettings, members, out var match, msg.Content, msg.Attachments.Length > 0, context.AllowAutoproxy); await ctx.Reply("I'm not sure why this message was not proxied, sorry."); } catch (ProxyService.ProxyChecksFailedException e) { await ctx.Reply($"{e.Message}"); } }