Part 3 - Making a lightbulb extension#

Extensions are a useful way to separate parts of your bot into different files, making it easier to manage.

So, let’s create an extension!

In your my_bot folder make a new folder named extensions.

Then in that folder create a file named info.py.

Your file structure should look like this now:

my_bot
├─ bot.py
├─ requirements.txt
├─ .env
│
└─ extensions
   ├─ info.py

In info.py paste the following:

 1from datetime import datetime
 2from typing import Optional
 3
 4import hikari
 5import lightbulb
 6
 7info_plugin = lightbulb.Plugin("Info")
 8
 9
10@info_plugin.command
11@lightbulb.app_command_permissions(dm_enabled=False)
12@lightbulb.option(
13    "user", "The user to get information about.", hikari.User, required=False
14)
15@lightbulb.command("userinfo", "Get info on a server member.", pass_options=True)
16@lightbulb.implements(lightbulb.SlashCommand)
17async def userinfo(
18    ctx: lightbulb.SlashContext, user: Optional[hikari.User] = None
19) -> None:
20    assert ctx.guild_id is not None
21
22    user = user or ctx.author
23    user = ctx.bot.cache.get_member(ctx.guild_id, user)
24
25    if not user:
26        await ctx.respond("That user is not in this server.")
27        return
28
29    created_at = int(user.created_at.timestamp())
30    joined_at = int(user.joined_at.timestamp())
31
32    roles = [f"<@&{role}>" for role in user.role_ids if role != ctx.guild_id]
33
34    embed = (
35        hikari.Embed(
36            title=f"User Info - {user.display_name}",
37            description=f"ID: `{user.id}`",
38            colour=0x3B9DFF,
39            timestamp=datetime.now().astimezone(),
40        )
41        .set_footer(
42            text=f"Requested by {ctx.author}",
43            icon=ctx.author.display_avatar_url,
44        )
45        .set_thumbnail(user.avatar_url)
46        .add_field(
47            "Bot?",
48            "Yes" if user.is_bot else "No",
49            inline=True,
50        )
51        .add_field(
52            "Created account on",
53            f"<t:{created_at}:d>\n(<t:{created_at}:R>)",
54            inline=True,
55        )
56        .add_field(
57            "Joined server on",
58            f"<t:{joined_at}:d>\n(<t:{joined_at}:R>)",
59            inline=True,
60        )
61        .add_field(
62            "Roles",
63            ", ".join(roles) if roles else "No roles",
64            inline=False,
65        )
66    )
67
68    await ctx.respond(embed)
69
70
71def load(bot: lightbulb.BotApp) -> None:
72    bot.add_plugin(info_plugin)

And in bot.py we’ll need to make a little change. On line 18, add:

bot.load_extensions_from("./extensions/")

So, now let’s run the bot with our new /userinfo command!

You should see a new line in your output:

I 2022-12-24 13:24:36,782 lightbulb.app: Extension loaded 'extensions.info'

Now let’s go and try out the command:

userinfo userinfo

Now to go through what everything does…

  • Line 7 - Create a plugin named Info which will be used to add our new command
    Read the docs - Plugins

  • Line 10 - Decorator to attach the following command to the plugin

  • Line 11 - Add some permissions to the command, disabling it from being accessible in DMs (we only want it to be run in servers)

  • Line 12-14 - Add a command option named “user” with a type of hikari.User that is not required and a description of “The user to get information about.
    Read the docs - Converters and Slash Command Options Types

  • Line 15 - Decorator to create the command, setting the name to “userinfo” and the description to “Get info on a server member.

  • Line 16 - Converts the decorated function into a slash command

  • Line 17-19 - The command’s function, which takes the parameters ctx and the option user
    Read the docs - lightbulb.Context
    Read the docs - hikari.User

  • Line 20 - Assert that ctx.guild_id is not None, because it never will be

  • Line 22 - If a user was not passed as an option (user will be None), we assign ctx.author to user

  • Line 23 - Get the Member object for that user from the bot’s cache

    Note

    This will return None if the user is not in the guild

  • Line 29-30 - Get the UNIX Timestamps for when the member created their account and joined the guild

    Note

    The rounding with int() is necessary, as Discord timestamps only work with integers, not floats

  • Line 32 - Get the member’s list of roles, excluding @everyone (the @everyone role’s ID is the guild’s ID), and format it into a list of role mentions

  • Line 35-40 - Make a Discord embed setting the title, description, colour and timestamp

  • Line 41-45 - Set the embed’s footer and thumbnail

  • Line 46-65 - Add fields to the embed, stating:

    • whether the user is a bot or not

    • when their account was created & when they joined the server, using Discord Timestamps

    • a list of roles the member has

  • Line 68 - Respond to the interaction with the embed (Read the docs - Context.respond)

  • Line 71-72 - The load function, to load the extension when the bot starts

    Note

    This load function is required in each extension

    Read the docs - Extensions