Esempio n. 1
0
    async def cpp(self, ctx, *, query):
        """
        This is still very experimental, as there is nothing of use in
        the implementation of the en.cppreference.com MediaWiki API, so
        this relies on crawling HTML. This may be relatively slow and
        buggy.
        """

        async with ctx.typing():
            book = neko.Book(ctx)

            results = await self.search_for(query)

            curr_page = None

            for i, result in enumerate(results):
                if curr_page is None:
                    curr_page = neko.Page(
                        title=f'Search results for `{query}`',
                        url=f'http://en.cppreference.com',
                        color=neko.random_colour())

                curr_page.add_field(name=result.name,
                                    value='\n'.join((result.desc, result.url)))

                if len(curr_page.fields) > 2 or i + 1 >= len(results):
                    book += curr_page
                    curr_page = None

        if not len(book):
            await ctx.send('No results', delete_after=10)
        else:
            await book.send()
Esempio n. 2
0
    async def pay_respects(self, ctx, *, _unused_what=None):
        # Filters out mention syntax. We cant do this from ctx directly
        # sadly, at least I don't think.
        what = ctx.message.clean_content
        # Remove the command prefix.
        # Todo: fix so this isn't aids when I am at an IDE...
        what = what[4:].strip()

        async with ctx.bot.postgres_pool.acquire() as conn:
            # Performs the increment server-side.
            await conn.execute('''
                UPDATE nekozilla.uncategorised_stuff 
                SET value_data = ((
                    SELECT value_data::INT FROM nekozilla.uncategorised_stuff 
                    WHERE key_name = 'respects_paid' 
                    LIMIT 1) + 1)::VARCHAR
                WHERE key_name = 'respects_paid';
                ''')
            total = await conn.fetchval('''
                SELECT value_data FROM nekozilla.uncategorised_stuff
                WHERE key_name = 'respects_paid'
                LIMIT 1; 
                ''')

        title = f'{ctx.author.display_name} has paid their respects '

        if what:
            title += f'for {what}'

        embed = neko.Page(title=title,
                          description=f'Total: {total}',
                          color=0x54c571)

        await ctx.send(embed=embed)
Esempio n. 3
0
    async def xkcd(self, ctx, number=None):
        if number == '-1':
            await ctx.send('https://xkcd.com/chesscoaster/')
        elif number == '0':
            await ctx.send('http://wiki.xkcd.com/geohashing/Main_Page')
        else:
            with ctx.typing():
                comic = await self.get_comic(number)

            date = neko.pluralize(int(comic.day), method='th')
            date += f' {months[int(comic.month) - 1]} {comic.year}'

            page = neko.Page(title=comic.title + ' #' + str(comic.num),
                             url='https://xkcd.com/' + str(comic.num),
                             description=date + '\n\n' + comic.alt,
                             colour=0xFFFFFF)  # XKCD white.

            page.set_author(
                name='xkcd',
                url='https://xkcd.com',
                # Sue me.
                icon_url='https://pbs.twimg.com/profile_images'
                '/602808103281692673/8lIim6cB_400x400.png')

            page.set_image(url=comic.img)

            await ctx.send(embed=page)
Esempio n. 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()
Esempio n. 5
0
    async def gen_front_page(self, ctx: neko.Context) -> neko.Page:
        """
        Generates an about page. This is the first page of the help
        pagination.

        :param ctx: the command context.
        """

        desc = f'{neko.__copyright__} under the {neko.__license__} license.\n\n'

        # Gets the docstring for the root module if there is one.
        doc_str = inspect.getdoc(neko)
        doc_str = inspect.cleandoc(doc_str if doc_str else '')
        desc += neko.remove_single_lines(doc_str)

        page = neko.Page(title=f'{neko.__title__} v{neko.__version__}',
                         description=desc,
                         color=default_color,
                         url=neko.__repository__)

        page.set_thumbnail(url=self.bot.user.avatar_url)

        page.add_field(name='Notable contributors',
                       value=', '.join(neko.__contributors__))

        page.add_field(name='Thanks to', value=neko.__thanks__, inline=False)

        page.add_field(name='Repository', value=neko.__repository__)

        cmds = sorted(self.bot.commands, key=lambda c: c.name)
        cmds = [
            await self.format_command_name(cmd, ctx) for cmd in cmds
            if await should_show(cmd, ctx)
        ]

        page.add_field(name='Available commands',
                       value=', '.join(cmds),
                       inline=False)

        page.add_field(
            name='Notations used in this help utility',
            value='- ~~strike~~ - this command is disabled, or cannot be run '
            'by your user, or in the current location.\n'
            '- _italics_ - this command is usually hidden from the main '
            'list.\n'
            '- starred\* - this command has sub-commands defined.',
            inline=False)

        return page
Esempio n. 6
0
    async def lmgtfy(self, ctx, *, query):
        """
        Garbage question = garbage answer.

        Call `lmgtfyd` to destroy your initial message.
        """
        frag = urllib.parse.urlencode({'q': query})

        if ctx.invoked_with == 'lmgtfyd':
            await ctx.message.delete()

        embed = neko.Page(title=query.title(),
                          description=random.choice(tasters),
                          color=neko.random_color(),
                          url=f'http://lmgtfy.com?{frag}')

        await ctx.send(embed=embed)
Esempio n. 7
0
    async def find_the_iss(self, ctx):
        """
        A very crappy and slow command to show you the ISS current location.
        """

        with ctx.channel.typing():
            # Plot the first point
            with io.BytesIO() as b:
                res = await self.bot.request(
                    'GET', 'https://api.wheretheiss.at/v1/satellites/25544')

                data = await res.json()
                image_fut = self.plot(data['latitude'], data['longitude'], b)

                assert isinstance(data, dict), 'I...I don\'t understand...'

                long = data['longitude']
                lat = data['latitude']
                time = datetime.datetime.fromtimestamp(data['timestamp'])
                altitude = data['altitude']
                velocity = data['velocity']

                is_day = data['visibility'] == 'daylight'

                desc = '\n'.join([
                    f'**Longitude**: {long:.3f} °E',
                    f'**Latitude**: {abs(lat):.3f} °{"N" if lat >= 0 else "S"}',
                    f'**Altitude**: {altitude:.3f} km',
                    f'**Velocity**: {velocity:.3f} km/h',
                    f'**Timestamp**: {time} UTC'
                ])

                embed = neko.Page(
                    title='International space station location',
                    description=desc,
                    color=0xFFFF00 if is_day else 0x0D293B,
                    url='http://www.esa.int/Our_Activities/Human_Spaceflight'
                    '/International_Space_Station'
                    '/Where_is_the_International_Space_Station ')

                embed.set_footer(text='Data provided by whereistheiss.at')

                await image_fut
                file = discord.File(b, 'iss.png')

                await ctx.send(file=file, embed=embed)
Esempio n. 8
0
    async def discord(self, ctx: neko.Context):
        """
        Gets a list of all Discord systems, and their service
        status.

        Lists of upcoming scheduled maintenances and unresolved
        incidents will be implemented eventually.
        """
        book = neko.Book(ctx)

        async with ctx.message.channel.typing():

            bot = ctx.bot
            """
            status = await self.get_status(bot)
            components = await self.get_components(bot)
            incidents = await self.get_incidents(bot)
            sms = await self.get_scheduled_maintenances(bot)
            """

            stat_res, comp_res, inc_res, sms_res = await asyncio.gather(
                bot.http_pool.get(get_endpoint('summary.json')),
                bot.http_pool.get(get_endpoint('components.json')),
                bot.http_pool.get(get_endpoint('incidents.json')),
                bot.http_pool.get(get_endpoint('scheduled-maintenances.json')))

            status, components, incidents, sms = await asyncio.gather(
                self.get_status(stat_res), self.get_components(comp_res),
                self.get_incidents(inc_res),
                self.get_scheduled_maintenances(sms_res))

            # Make the front page!
            if status['indicator'] == 'None':
                desc = ''
            else:
                desc = f'**{status["indicator"]}**\n\n'

            desc += (f'{status["description"]}\n\n'
                     f'Last updated: {friendly_date(status["updated_at"])}.')

            if not incidents['unresolved']:
                color = status['color']
            else:
                color = get_impact_color(
                    find_highest_impact(incidents['unresolved']))
            """
            PAGE 1
            ------

            Overall status
            """
            page = neko.Page(title='Discord API Status',
                             description=desc,
                             color=color,
                             url=status['url'])

            if incidents['unresolved']:
                first = incidents['unresolved'][0]
                name = first['Name']
                body = make_incident_body(first)

                page.add_field(name=name, value=body, inline=False)

            book += page
            """
            PAGE 2
            ------

            Overall status again, but with more information on showcase
            components.
            """
            page = neko.Page(title='Discord API Status',
                             description=desc,
                             color=color,
                             url=status['url'])

            for component in components['showcase']:
                title = component.pop('Name')
                desc = []
                for k, v in component.items():
                    line = f'**{k}**: '
                    if isinstance(v, datetime.datetime):
                        line += friendly_date(v)
                    else:
                        line += str(v)
                    desc.append(line)
                page.add_field(name=title, value='\n'.join(desc), inline=False)

            book += page
            """
            PAGE 3
            ======

            Non showcase components
            """
            page = None

            fields = 0
            for component in components['rest']:
                if fields >= max_fields:
                    book += page
                    page = None
                    fields = 0

                if page is None:
                    page = neko.Page(
                        title='Other components',
                        description='Other minor components for Discord.',
                        color=color,
                        url=status['url'])

                title = component.pop('Name')
                desc = []
                for k, v in component.items():
                    line = f'**{k}**: '
                    if isinstance(v, datetime.datetime):
                        line += friendly_date(v)
                    else:
                        line += str(v)
                    desc.append(line)

                page.add_field(name=title, value='\n'.join(desc), inline=False)
                fields += 1

            if fields > 0:
                book += page
            """
            PAGE 3
            ======
            
            Incidents.
            """
            page = neko.Page(title='Unresolved incidents', color=color)

            if incidents['unresolved']:
                incident = incidents['unresolved'][0]

                name = f'**{incident["Name"]}**'
                desc = make_incident_body(incident)

                page.description = name + '\n\n' + desc.strip()

            for incident in incidents['unresolved'][1:3]:
                body = make_incident_body(incident)
                name = incident['Name']

                body = name + '\n\n' + body

                page.add_field(name='\u200b', value=body.strip())

            book += page
            """
            PAGE 4
            ======

            Resolved incidents.
            """
            page = neko.Page(
                title='Resolved incidents',
                color=color,
            )

            if incidents['resolved']:
                incident = incidents['resolved'][0]

                name = f'**{incident["Name"]}**'
                desc = make_incident_body(incident)
                page.description = name + '\n\n' + desc.strip()

            # Add the next three most recent.
            for incident in incidents['resolved'][1:3]:
                body = make_incident_body(incident)
                name = f'**{incident["Name"]}**'

                body = name + '\n\n' + body.strip()

                page.add_field(name='\u200b', value=body)

            book += page

            await book.send()
Esempio n. 9
0
    async def get_word(self, ctx, *, word: str):
        """
        Gets a definition of a given word or phrase from WordNik
        """
        def _define():
            # Much complex. Very definition. Such API! Wow!
            api = wordapi.WordApi(self.client)

            # *prays to god this isn't lazy iterative.
            return api.getDefinitions(word,
                                      sourceDictionaries=wordnik_dictionaries,
                                      includeRelated=True)

        with ctx.typing():
            words: typing.List[
                wordnik_definition.Definition] = await ctx.bot.do_job_in_pool(
                    _define)

        # Attempt to favour gcide and wordnet, as they have better definitions
        # imho.
        # Fixes #9
        if not words:
            await ctx.send('I couldn\'t find a definition for that.',
                           delete_after=10)
        else:

            front = []
            back = []

            for word in words:
                if word.sourceDictionary in ('gcide', 'wordnet'):
                    front.append(word)
                else:
                    back.append(word)

            # Re-join.
            words = [*front, *back]

            words: typing.List[wordnik_definition.Definition] = [
                word for word in words
                if not word.sourceDictionary.startswith('ahd')
            ]

            # Max results to get is 100.
            max_count = min(100, len(words))

            book = neko.Book(ctx)

            for i in range(0, max_count):
                word = words[i]

                text = ''
                if word.partOfSpeech:
                    text += f'**{word.partOfSpeech}** '

                if word.text:
                    text += word.text

                if word.extendedText:
                    text += '\n\n'
                    text += word.extendedText

                page = neko.Page(title=neko.capitalize(word.word),
                                 description=text,
                                 color=neko.random_colour())

                if word.exampleUses:
                    example = word.exampleUses[0]
                    ex_text = neko.ellipses(example.text, 800)

                    page.add_field(name='Example', value=ex_text, inline=False)

                if word.relatedWords:

                    related = ', '.join(
                        [', '.join(rw.words) for rw in word.relatedWords])

                    page.add_field(name='Synonyms', value=related)

                if word.textProns:
                    pron = '\n'.join([tp.raw for tp in word.textProns])
                    pron = neko.ellipses(pron, 400)

                    page.add_field(
                        name='Pronunciations',
                        value=pron,
                    )

                if word.score:
                    page.add_field(name='Scrabble score',
                                   value=word.score.value)

                if word.labels:
                    labels = ', '.join(label.text for label in word.labels)
                    labels = neko.ellipses(labels, 300)

                    page.add_field(name='Labels', value=labels)

                if word.notes:
                    notes = []
                    for j, note in enumerate(word.notes):
                        notes.append(f'[{j+1}] {note.value}')

                    notes = neko.ellipses('\n\n'.join(notes), 300)

                    page.add_field(name='Notes', value=notes)

                if word.attributionText:
                    attr = word.attributionText
                else:
                    attr = ('Extracted from '
                            f'{neko.capitalise(word.sourceDictionary)}')

                page.set_footer(text=attr)

                book += page

            await book.send()
Esempio n. 10
0
    async def urban(self, ctx, *, phrase: str = None):
        """
        Searches urban dictionary for the given phrase or word.

        If no word is specified, we pick a few random entries.
        """
        with ctx.typing():
            if phrase:
                api, user = ud_define_def
                resp = await ctx.bot.request('GET',
                                             api,
                                             params={'term': phrase})
                user = user + '?' + urllib.parse.urlencode({'term': phrase})
            else:
                api, user = ud_random_def
                resp = await ctx.bot.request('GET', api)

        # Discard the rest, only be concerned with upto the first 10 results.
        resp = await resp.json()
        results = resp['list'][0:10]

        book = neko.Book(ctx)

        for definition in results:
            page = neko.Page(title=definition['word'].title(),
                             description=definition['definition'],
                             color=0xFFFF00,
                             url=user)

            page.add_field(name='Example of usage',
                           value=definition['example'],
                           inline=False)

            page.add_field(name='Author', value=definition['author'])

            ups = definition['thumbs_up']
            downs = definition['thumbs_down']

            page.add_field(
                name=f'\N{THUMBS UP SIGN} {ups}',
                # No content (little discord trick)
                value=f'\N{THUMBS DOWN SIGN} {downs}ᅠ')

            page.set_thumbnail(url=ud_thumb_url)

            if 'tags' in resp:
                # Seems the tags can contain duplicates. Quick messy solution
                # is to pass it into a set first.
                page.set_footer(text=' '.join({*resp['tags']}),
                                icon_url=ud_icon_url)
            else:
                page.set_footer(text=definition['permalink'],
                                icon_url=ud_icon_url)

            book += page

        if book.pages:
            await book.send()
        else:
            await ctx.send('I couldn\'t find a definition for that.',
                           delete_after=10)
Esempio n. 11
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)
Esempio n. 12
0
    async def gen_spec_page(self, ctx: neko.Context,
                            cmd: neko.NekoCommand) -> neko.Page:
        """
        Given a context and a command, generate a help page entry for the
        command.

        :param ctx: the context to use to determine if we can run the command
                here.
        :param cmd: the command to generate the help page for.
        :return: a book page.
        """
        pfx = self.bot.command_prefix
        fqn = await self.format_command_name(cmd, ctx, is_full=True)
        brief = cmd.brief if cmd.brief else 'Whelp! No info here!'
        doc_str = neko.remove_single_lines(cmd.help)
        usages = cmd.usage.split('|') if cmd.usage else ''
        usages = map(lambda u: f'• {pfx}{cmd.qualified_name} {u}', usages)
        usages = '\n'.join(sorted(usages))
        aliases = sorted(cmd.aliases)
        cooldown = getattr(cmd, '_buckets')

        if cooldown:
            cooldown: neko.Cooldown = getattr(cooldown, '_cooldown')

        if cmd.parent:
            super_command = await self.format_command_name(cmd.parent, ctx)
        else:
            super_command = None

        # noinspection PyUnresolvedReferences
        can_run = await cmd.can_run(ctx)

        if isinstance(cmd, neko.GroupMixin):

            async def sub_cmd_map(c):
                c = await self.format_command_name(c, ctx, is_full=True)
                c = f'• {c}'
                return c

            # Cast to a set to prevent duplicates for aliases. Hoping this
            # fixes #9 again.
            # noinspection PyUnresolvedReferences
            sub_commands = cmd.walk_commands()
            sub_commands = [await sub_cmd_map(c) for c in sub_commands]
            sub_commands = sorted(sub_commands)
        else:
            sub_commands = []

        if getattr(cmd, 'enabled', False) and can_run:
            color = default_color
        elif not can_run:
            color = 0xFFFF00
        else:
            color = 0xFF0000

        page = neko.Page(title=await self.format_command_name(cmd,
                                                              ctx,
                                                              is_full=True),
                         description=brief,
                         color=color)

        if doc_str:
            page.add_field(name='More info', value=doc_str, inline=False)

        if usages:
            page.add_field(name='Usage', value=usages, inline=False)

        if aliases:
            page.add_field(name='Aliases', value=', '.join(aliases))

        if cooldown:
            timeout = cooldown.per
            if timeout.is_integer():
                timeout = int(timeout)

            string = (
                f'{neko.capitalise(cooldown.type.name)}-scoped '
                f'{neko.pluralise(cooldown.rate, "request", method="per app")} '
                f'with timeout of {neko.pluralise(timeout, "second")}.')

            page.add_field(name='Cooldown policy', value=string)

        if sub_commands:
            page.add_field(name='Child commands',
                           value='\n'.join(sub_commands))

        if super_command:
            page.add_field(name='Parent command', value=super_command)

        if not can_run and cmd.enabled:
            page.set_footer(
                text='You do not hve permission to run the command here... '
                'Sorry!')
        elif not cmd.enabled:
            page.set_footer(
                text='This command has been disabled globally by the dev.')

        return page
Esempio n. 13
0
def make_colour_embed(r, g, b, a=255):
    """
    Generates an embed to describe the given RGB(A) colour, then returns it.
    """
    # % alpha
    pct_a = round(100. * a / 255., 2)

    embed = neko.Page(color=discord.Color.from_rgb(r, g, b))

    hex_str = utils.to_hex(r, g, b, a).upper()
    short_hex = utils.to_short_hex(r, g, b, a)

    if short_hex:
        short_hex = short_hex.upper()

    rf, gf, bf, af = utils.to_float(r, g, b, a)

    hsl_h, hsl_s, hsl_l = utils.to_hsl(r, g, b)
    hsl_h = f'{hsl_h:.0f}\N{DEGREE SIGN}'
    hsl_s = f'{hsl_s:.0f}%'
    hsl_l = f'{hsl_l:.0f}%'

    hsv_h, hsv_s, hsv_v = utils.to_hsv(r, g, b)
    hsv_h = f'{hsv_h:.0f}\N{DEGREE SIGN}'
    hsv_s = f'{hsv_s:.0f}%'
    hsv_v = f'{hsv_v:.0f}%'

    cmyk_c, cmyk_m, cmyk_y, cmyk_k = utils.to_cmyk(r, g, b)
    cmyk_c = round(cmyk_c, 2)
    cmyk_m = round(cmyk_m, 2)
    cmyk_y = round(cmyk_y, 2)
    cmyk_k = round(cmyk_k, 2)

    title = utils.HtmlNames.from_value((r, g, b))
    if title:
        # Title case!
        title = title.title()

        if not short_hex:
            title += f' ({hex_str})'
        else:
            title += f' ({hex_str}, {short_hex})'
    else:
        title = hex_str

    embed.title = title

    if a < 255:
        embed.description = f'{pct_a:.0f}% opacity'

    embed.add_field(name='RGB and RGBA',
                    value=f'RGBb\t{r, g, b}\nRGBAb {r, g, b, a}\n'
                    f'RGBf \t{rf, gf, bf}\nRGBAf  {rf, gf, bf, af}')

    embed.add_field(name='Other systems',
                    value=f'CMYK   ({cmyk_c}, {cmyk_m}, {cmyk_y}, {cmyk_k})\n'
                    f'HSL\t\t({hsl_h}, {hsl_s}, {hsl_l})\n'
                    f'HSV\t\t({hsv_h}, {hsv_s}, {hsv_v})')

    footer = (
        f'This is{" " if utils.is_web_safe(r,g,b,a) else " not "}web-safe.')

    if a < 255:
        # Disclaimer.
        footer += ' Embed colour does not take into account alpha. '

    footer = ('Values may not directly match the input, as they are '
              'limited by the gamut of 32-bit RGBA colour-space.')

    embed.set_footer(text=footer)

    return embed