示例#1
0
    async def cmyk_float(self, ctx, c, m, y, k):
        """
        Displays info on a colour given in the CMYK (cyan-magenta-yellow-key)
        colour space.

        "Key" may also be defined as "black".
        """
        try:
            r, g, b = utils.from_cmyk(c, m, y, k)
        except (ValueError, TypeError) as ex:
            raise neko.NekoCommandError(str(ex))
        else:
            await single_colour_response(ctx, r, g, b)
示例#2
0
    async def lookup(self, ctx, *, string: str):
        """
        This is the opposite of `charinfo`. It will look up a given string as
        a unicode character name and if it finds a character matching the
        input, it will show unicode information about it.
        """
        try:
            character = unicodedata.lookup(string.upper())
            await ctx.send('\n'.join(_unicode_table(character)))

        except KeyError:
            raise neko.NekoCommandError('No character exists for that '
                                        'description.')
示例#3
0
    async def rgba_byte(self, ctx, r, g, b, a='255'):
        """
        If alpha is omitted, then it gets the value of 255 by default.

        All values must be in the range 0 ≤ x < 256.
        """
        try:
            r, g, b, a = (utils.ensure_int_in_0_255(x) for x in (r, g, b, a))
            for x in (r, g, b, a):
                if not 0 <= x < 256:
                    raise TypeError('Must be in range [0, 256)')
            await single_colour_response(ctx, r, g, b, a)
        except (ValueError, TypeError) as ex:
            raise neko.NekoCommandError(ex)
示例#4
0
    async def tag_inspect(self, ctx, tag_name):
        """
        This is only runnable by the bot owner.
        """
        async with ctx.bot.postgres_pool.acquire() as conn:
            book = neko.Book(ctx)
            with ctx.typing():
                tag_name = tag_name.lower()
                results = await conn.fetch(
                    '''
                    SELECT * FROM nekozilla.tags 
                    LEFT JOIN nekozilla.tags_attach
                    ON pk = tag_pk
                    WHERE LOWER(name) = ($1);
                    ''', tag_name)

            if not results:
                raise neko.NekoCommandError('No results.')

            for result in results:
                data = dict(result)
                content = data.pop('content')
                author = data.pop('author')
                file = data.pop('file_name')

                # Don't want to send this!!!
                data.pop('b64data')

                user: discord.User = await ctx.bot.get_user_info(author)
                data['author'] = ' '.join([
                    'BOT' if user.bot else '', user.display_name,
                    str(user.id)
                ])

                page = neko.Page(title=f'`{data.pop("name")}`',
                                 description=content)

                page.set_thumbnail(url=user.avatar_url)

                if file is not None:
                    page.set_footer(text=f'Attached file: {file}')

                page.add_field(name='Attributes',
                               value='\n'.join(f'**{k}**: `{v}`'
                                               for k, v in data.items()))

                book += page

            await book.send()
示例#5
0
    async def palette(self, ctx, *, colours):
        """
        This is experimental, and slow. Thus, there is a cooldown restriction
        on this command to prevent slowing the bot down for everyone else.

        This only supports RGB-hex strings currently, as well as RGB-bytes,
        and HTML colour names. Anything that contains spaces must be surrounded
        by quotes.
        """
        try:
            with ctx.typing(), io.BytesIO() as fp:
                # Parse args
                colours = neko.parse_quotes(colours)

                await ctx.bot.do_job_in_pool(utils.make_palette, fp, *colours)

                file = discord.File(fp, 'palette.png')

                await ctx.send(file=file)
        except (ValueError, TypeError, KeyError) as ex:
            string = f'No match: {ex}'
            raise neko.NekoCommandError(string)
示例#6
0
    async def tag_add_global(self, ctx: neko.Context, tag_name, *, content):
        """
        This is only currently accessible by the bot owner.
        """
        attachment = ctx.message.attachments

        if ctx.author.id == self.bot.owner_id:
            self.tag_add.reset_cooldown(ctx)

        if attachment:
            if len(attachment) > 1:
                raise neko.NekoCommandError(
                    'Can only upload at most one file.')
            else:
                # Get the first attachment.
                attachment: discord.Attachment = attachment[0]

                if attachment.size > _MAX_IMAGE_SIZE:
                    raise neko.NekoCommandError(
                        f'You can upload a maximum of {_MAX_IMAGE_SIZE/1024} '
                        'KiB per tag.')
        else:
            attachment = None

        tag_name = tag_name.lower()

        # First, make sure tag is valid.
        if tag_name in self.invalid_tag_names:
            raise neko.NekoCommandError('Invalid tag name')

        async with self.bot.postgres_pool.acquire() as conn:
            # This is a multiple part query with a select and two inserts.
            # Transaction usage means if something else f***s up then ALL
            # changes made up to that point are deferred safely and the
            # database is left in a stable state.
            async with conn.transaction(isolation='serializable',
                                        readonly=False,
                                        deferrable=False):
                with ctx.channel.typing():
                    # Next, see if the tag already exists.
                    existing = await conn.fetch(
                        '''
                        SELECT 1 FROM nekozilla.tags
                        WHERE LOWER(name) = ($1) AND guild IS NULL;
                        ''', tag_name)
                    if len(existing) > 0:
                        raise neko.NekoCommandError('Tag already exists')

                    await conn.execute(
                        '''
                        INSERT INTO nekozilla.tags 
                            (name, author, guild, is_nsfw, content) 
                        VALUES (($1), ($2), NULL, ($3), ($4));
                        ''', tag_name, ctx.author.id, ctx.channel.nsfw,
                        content)

                    # If we have an attachment, we must first fetch it.
                    if attachment is not None:
                        resp = await self.bot.request('GET', attachment.url)
                        data: bytes = await resp.read()

                        base64_img = base64.b64encode(data)

                        # Discord or Discord.py removes my bloody file extension
                        # from the file name!!!!! REEE!

                        self.logger.info(
                            f'{ctx.author} uploaded {tag_name} {attachment.url}'
                            f' in {ctx.guild}.{ctx.channel}. It was global.')
                        url: str = attachment.url
                        start_index = url.find(attachment.filename)
                        if start_index != -1:
                            attachment.filename = url[start_index:]

                        await conn.execute(
                            '''
                            INSERT INTO nekozilla.tags_attach
                                (tag_pk, file_name, b64data)
                            VALUES (
                              (
                                -- TODO: make this not shit.
                                SELECT pk FROM nekozilla.tags
                                WHERE 
                                  name = ($1) AND
                                  guild IS NULL AND 
                                  author = ($2)
                                LIMIT 1
                              ),
                              ($3), ($4)
                            );
                            ''', tag_name, ctx.author.id, attachment.filename,
                            base64_img.decode())

                await self._del_msg_soon(ctx, await
                                         ctx.send('Added globally.'))
示例#7
0
    async def tag_group(self, ctx: neko.Context, tag_name=None, *args):
        """
        Displays the tag if it can be found. The local tags are searched
        first, and then the global tags.

        If we start the tag with an "!", then we try the global tags list first
        instead.

        **22nd Jan 2018**:

        I have added a few meta-commands into the mix. From now on, the
        following can be added into a tag, and it will be resolved when the tag
        is retrieved:

        ${args} -> any text you put after the tag name.\r
        ${channel} -> the channel name.\r
        ${channel_mention} -> the channel name, but as a mention.\r
        ${channel_id} -> the channel snowflake ID.\r
        ${author} -> the display name of whoever invoked the tag.\r
        ${author_mention} -> mentions whoever invoked the tag.\r
        ${author_discriminator} -> shows the discriminator of whoever invoked
        the tag.\r
        ${author_username} -> shows the username of whoever invoked the tag.
        ${author_id} -> shows the snowflake ID for the user who invoked the tag.
        ${guild} -> shows the name of the guild (server) the tag is called on.
        ${guild_id} -> shows the ID of the guild (server) the tag is called on.
        """
        if tag_name is None:
            book = neko.PaginatedBook(ctx=ctx, title='Tags', max_lines=15)

            desc = f'Run {ctx.prefix}help tag <command> for more info.\n\n'

            page = neko.Page(title='Tag commands')

            cmds = {*self.tag_group.walk_commands()}
            for cmd in copy.copy(cmds):
                # Remove any commands we cannot run.
                if not await cmd.can_run(ctx) or cmd.hidden:
                    cmds.remove(cmd)

            # Generate strings.
            cmds = {
                '**' + ' '.join(cmd.qualified_name.split(' ')[1:]) + '** - ' +
                cmd.brief
                for cmd in cmds
            }

            for line in sorted(cmds):
                desc += f'{line}\n'

            desc += '\nThe following pages will list the available tags.'

            page.description = desc

            book += page

            async with ctx.typing():
                await self._add_tag_list_to_pag(ctx, book)

            await book.send()
            return

        if tag_name.startswith('!'):
            tag_name = tag_name[1:]
            local_first = True
        else:
            local_first = False

        async with self.bot.postgres_pool.acquire() as conn:
            with ctx.channel.typing():
                results = await conn.fetch(
                    '''
                    SELECT 
                      content,
                      file_name,
                      b64data
                    FROM nekozilla.tags
                    LEFT OUTER JOIN nekozilla.tags_attach
                    ON pk = tag_pk
                    WHERE
                      name = ($1) AND
                      (guild = ($2) OR guild IS NULL) AND 
                      -- This will be TRUE OR FALSE -> TRUE if chan is NSFW
                      -- and FALSE OR FALSE -> FALSE if chan is NOT NSFW.
                      is_nsfw = (($3) OR FALSE) 
                    ORDER BY guild NULLS LAST;
                    ''', tag_name, ctx.guild.id, ctx.channel.nsfw)

            if not results:
                raise neko.NekoCommandError('No tag found with that name.')
            else:
                first = results.pop(0 if local_first else -1)
                content = first['content']
                attachment_name = first['file_name']
                attachment_data = first['b64data']

                if attachment_name is not None and attachment_data is not None:
                    # Decode attachment data.
                    attachment_data = base64.b64decode(attachment_data)

                # We allow a few dynamic bits and pieces.
                # TODO: document this.
                replacements = {
                    ('${args}', ' '.join(str(arg) for arg in args)),
                    ('${channel}', str(ctx.channel.name)),
                    ('${channel_mention}', f'<#{ctx.channel.id}>'),
                    ('${channel_id}', str(ctx.channel.id)),
                    ('${author}', str(ctx.author.display_name)),
                    ('${author_mention}', str(ctx.author.mention)),
                    ('${author_discriminator}', str(ctx.author.discriminator)),
                    ('${author_username}', str(ctx.author.name)),
                    ('${author_id}', str(ctx.author.id)),
                    ('${guild}', str(ctx.guild.name)),
                    ('${guild_id}', str(ctx.guild.id))
                }

                for replacement in replacements:
                    content = content.replace(*replacement)

                if attachment_name is not None and attachment_data is not None:
                    with io.BytesIO(
                            initial_bytes=attachment_data) as att_bytes:
                        att_bytes.seek(0)
                        file = discord.File(att_bytes,
                                            filename=attachment_name)
                        await ctx.send(content, file=file)
                else:
                    await ctx.send(content)