コード例 #1
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);
    }
コード例 #2
0
    private async Task OnEventReceived(int shardId, IGatewayEvent evt)
    {
        // we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent
        await _cache.HandleGatewayEvent(evt);

        var userId = await _cache.GetOwnUser();

        await _cache.TryUpdateSelfMember(userId, evt);

        await OnEventReceivedInner(shardId, evt);
    }
コード例 #3
0
    private async Task <Webhook?> GetOrCreateWebhook(ulong channelId)
    {
        _logger.Debug("Webhook for channel {Channel} not found in cache, trying to fetch", channelId);
        _metrics.Measure.Meter.Mark(BotMetrics.WebhookCacheMisses);

        _logger.Debug("Finding webhook for channel {Channel}", channelId);
        var webhooks = await FetchChannelWebhooks(channelId);

        // If the channel has a webhook created by PK, just return that one
        var ourUserId = await _cache.GetOwnUser();

        var ourWebhook = webhooks.FirstOrDefault(hook => IsWebhookMine(ourUserId, hook));

        if (ourWebhook != null)
        {
            return(ourWebhook);
        }

        // We don't have one, so we gotta create a new one
        // but first, make sure we haven't hit the webhook cap yet...
        if (webhooks.Length >= 10)
        {
            throw new PKError(
                      "This channel has the maximum amount of possible webhooks (10) already created. A server admin must delete one or more webhooks so PluralKit can create one for proxying.");
        }

        return(await DoCreateWebhook(channelId));
    }
コード例 #4
0
    // todo: move this somewhere else
    private async Task <PermissionSet> GetPermissionsInLogChannel(Channel channel)
    {
        var guild = await _cache.TryGetGuild(channel.GuildId.Value);

        if (guild == null)
        {
            guild = await _rest.GetGuild(channel.GuildId.Value);
        }

        var guildMember = await _cache.TryGetSelfMember(channel.GuildId.Value);

        if (guildMember == null)
        {
            guildMember = await _rest.GetGuildMember(channel.GuildId.Value, await _cache.GetOwnUser());
        }

        var perms = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember);

        return(perms);
    }
コード例 #5
0
    private async Task OnEventReceived(int shardId, IGatewayEvent evt)
    {
        // we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent
        await _cache.HandleGatewayEvent(evt);

        var userId = await _cache.GetOwnUser();

        await _cache.TryUpdateSelfMember(userId, evt);

        // HandleEvent takes a type parameter, automatically inferred by the event type
        // It will then look up an IEventHandler<TypeOfEvent> in the DI container and call that object's handler method
        // For registering new ones, see Modules.cs
        if (evt is MessageCreateEvent mc)
        {
            await HandleEvent(shardId, mc);
        }
        if (evt is MessageUpdateEvent mu)
        {
            await HandleEvent(shardId, mu);
        }
        if (evt is MessageDeleteEvent md)
        {
            await HandleEvent(shardId, md);
        }
        if (evt is MessageDeleteBulkEvent mdb)
        {
            await HandleEvent(shardId, mdb);
        }
        if (evt is MessageReactionAddEvent mra)
        {
            await HandleEvent(shardId, mra);
        }
        if (evt is InteractionCreateEvent ic)
        {
            await HandleEvent(shardId, ic);
        }
    }
コード例 #6
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) { }
    }
コード例 #7
0
    public async Task Invite(Context ctx)
    {
        var clientId = _botConfig.ClientId ?? await _cache.GetOwnUser();

        var permissions =
            PermissionSet.AddReactions |
            PermissionSet.AttachFiles |
            PermissionSet.EmbedLinks |
            PermissionSet.ManageMessages |
            PermissionSet.ManageWebhooks |
            PermissionSet.ReadMessageHistory |
            PermissionSet.SendMessages;

        var invite =
            $"https://discord.com/oauth2/authorize?client_id={clientId}&scope=bot%20applications.commands&permissions={(ulong)permissions}";

        var botName = _botConfig.IsBetaBot ? "PluralKit Beta" : "PluralKit";
        await ctx.Reply($"{Emojis.Success} Use this link to add {botName} to your server:\n<{invite}>");
    }
コード例 #8
0
    public async Task PermCheckGuild(Context ctx)
    {
        Guild guild;
        GuildMemberPartial senderGuildUser = null;

        if (ctx.Guild != null && !ctx.HasNext())
        {
            guild           = ctx.Guild;
            senderGuildUser = ctx.Member;
        }
        else
        {
            var guildIdStr = ctx.RemainderOrNull() ??
                             throw new PKSyntaxError("You must pass a server ID or run this command in a server.");
            if (!ulong.TryParse(guildIdStr, out var guildId))
            {
                throw new PKSyntaxError($"Could not parse {guildIdStr.AsCode()} as an ID.");
            }

            try
            {
                guild = await _rest.GetGuild(guildId);
            }
            catch (ForbiddenException)
            {
                throw Errors.GuildNotFound(guildId);
            }

            if (guild != null)
            {
                senderGuildUser = await _rest.GetGuildMember(guildId, ctx.Author.Id);
            }
            if (guild == null || senderGuildUser == null)
            {
                throw Errors.GuildNotFound(guildId);
            }
        }

        var guildMember = await _rest.GetGuildMember(guild.Id, await _cache.GetOwnUser());

        // Loop through every channel and group them by sets of permissions missing
        var permissionsMissing      = new Dictionary <ulong, List <Channel> >();
        var hiddenChannels          = false;
        var missingEmojiPermissions = false;

        foreach (var channel in await _rest.GetGuildChannels(guild.Id))
        {
            var botPermissions     = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember);
            var webhookPermissions = PermissionExtensions.EveryonePermissions(guild, channel);
            var userPermissions    = PermissionExtensions.PermissionsFor(guild, channel, ctx.Author.Id, senderGuildUser);

            if ((userPermissions & PermissionSet.ViewChannel) == 0)
            {
                // If the user can't see this channel, don't calculate permissions for it
                // (to prevent info-leaking, mostly)
                // Instead, show the user that some channels got ignored (so they don't get confused)
                hiddenChannels = true;
                continue;
            }

            // We use a bitfield so we can set individual permission bits in the loop
            // TODO: Rewrite with proper bitfield math
            ulong missingPermissionField = 0;

            foreach (var requiredPermission in requiredPermissions)
            {
                if ((botPermissions & requiredPermission) == 0)
                {
                    missingPermissionField |= (ulong)requiredPermission;
                }
            }

            if ((webhookPermissions & PermissionSet.UseExternalEmojis) == 0)
            {
                missingPermissionField |= (ulong)PermissionSet.UseExternalEmojis;
                missingEmojiPermissions = true;
            }

            // If we're not missing any permissions, don't bother adding it to the dict
            // This means we can check if the dict is empty to see if all channels are proxyable
            if (missingPermissionField != 0)
            {
                permissionsMissing.TryAdd(missingPermissionField, new List <Channel>());
                permissionsMissing[missingPermissionField].Add(channel);
            }
        }

        // Generate the output embed
        var eb = new EmbedBuilder()
                 .Title($"Permission check for **{guild.Name}**");

        if (permissionsMissing.Count == 0)
        {
            eb.Description("No errors found, all channels proxyable :)").Color(DiscordUtils.Green);
        }
        else
        {
            foreach (var(missingPermissionField, channels) in permissionsMissing)
            {
                // Each missing permission field can have multiple missing channels
                // so we extract them all and generate a comma-separated list
                var missingPermissionNames = ((PermissionSet)missingPermissionField).ToPermissionString();

                var channelsList = string.Join("\n", channels
                                               .OrderBy(c => c.Position)
                                               .Select(c => $"#{c.Name}"));
                eb.Field(new Embed.Field($"Missing *{missingPermissionNames}*", channelsList.Truncate(1000)));
                eb.Color(DiscordUtils.Red);
            }
        }

        var footer = "";

        if (hiddenChannels)
        {
            footer += "Some channels were ignored as you do not have view access to them.";
        }
        if (missingEmojiPermissions)
        {
            if (hiddenChannels)
            {
                footer += " | ";
            }
            footer +=
                "Use External Emojis permissions must be granted to the @everyone role / Default Permissions.";
        }

        if (footer.Length > 0)
        {
            eb.Footer(new Embed.EmbedFooter(footer));
        }

        // Send! :)
        await ctx.Reply(embed : eb.Build());
    }
コード例 #9
0
ファイル: ReactionAdded.cs プロジェクト: xSke/PluralKit
    private async ValueTask TryHandleProxyMessageReactions(MessageReactionAddEvent evt)
    {
        // ignore any reactions added by *us*
        if (evt.UserId == await _cache.GetOwnUser())
        {
            return;
        }

        // Ignore reactions from bots (we can't DM them anyway)
        // note: this used to get from cache since this event does not contain Member in DMs
        // but we aren't able to get DMs from bots anyway, so it's not really needed
        if (evt.GuildId != null && evt.Member.User.Bot)
        {
            return;
        }

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

        // check if it's a command message first
        // since this can happen in DMs as well
        if (evt.Emoji.Name == "\u274c")
        {
            // in DMs, allow deleting any PK message
            if (channel.GuildId == null)
            {
                await HandleCommandDeleteReaction(evt, null);

                return;
            }

            var commandMsg = await _commandMessageService.GetCommandMessage(evt.MessageId);

            if (commandMsg != null)
            {
                await HandleCommandDeleteReaction(evt, commandMsg);

                return;
            }
        }

        // Proxied messages only exist in guild text channels, so skip checking if we're elsewhere
        if (!DiscordUtils.IsValidGuildChannel(channel))
        {
            return;
        }

        switch (evt.Emoji.Name)
        {
        // Message deletion
        case "\u274C":     // Red X
        {
            var msg = await _db.Execute(c => _repo.GetMessage(c, evt.MessageId));

            if (msg != null)
            {
                await HandleProxyDeleteReaction(evt, msg);
            }

            break;
        }

        case "\u2753":     // Red question mark
        case "\u2754":     // White question mark
        {
            var msg = await _db.Execute(c => _repo.GetMessage(c, evt.MessageId));

            if (msg != null)
            {
                await HandleQueryReaction(evt, msg);
            }

            break;
        }

        case "\U0001F514": // Bell
        case "\U0001F6CE": // Bellhop bell
        case "\U0001F3D3": // Ping pong paddle (lol)
        case "\u23F0":     // Alarm clock
        case "\u2757":     // Exclamation mark
        {
            var msg = await _db.Execute(c => _repo.GetMessage(c, evt.MessageId));

            if (msg != null)
            {
                await HandlePingReaction(evt, msg);
            }
            break;
        }
        }
    }