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