Part 3 - Making a lightbulb extension#

Extensions are a useful way of separating 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.option(
12    "user", "The user to get information about.", hikari.User, required=False
13)
14@lightbulb.command("userinfo", "Get info on a server member.", pass_options=True)
15@lightbulb.implements(lightbulb.PrefixCommand, lightbulb.SlashCommand)
16async def userinfo(ctx: lightbulb.Context, user: Optional[hikari.User] = None) -> None:
17    if not (guild := ctx.get_guild()):
18        await ctx.respond("This command may only be used in servers.")
19        return
20
21    user = user or ctx.author
22    user = ctx.bot.cache.get_member(guild, user)
23
24    if not user:
25        await ctx.respond("That user is not in the server.")
26        return
27
28    created_at = int(user.created_at.timestamp())
29    joined_at = int(user.joined_at.timestamp())
30
31    roles = (await user.fetch_roles())[1:]  # All but @everyone
32    roles = sorted(
33        roles, key=lambda role: role.position, reverse=True
34    )  # sort them by position, then reverse the order to go from top role down
35
36    embed = (
37        hikari.Embed(
38            title=f"User Info - {user.display_name}",
39            description=f"ID: `{user.id}`",
40            colour=0x3B9DFF,
41            timestamp=datetime.now().astimezone(),
42        )
43        .set_footer(
44            text=f"Requested by {ctx.author.username}",
45            icon=ctx.author.display_avatar_url,
46        )
47        .set_thumbnail(user.avatar_url)
48        .add_field(
49            "Bot?",
50            "Yes" if user.is_bot else "No",
51            inline=True,
52        )
53        .add_field(
54            "Created account on",
55            f"<t:{created_at}:d>\n(<t:{created_at}:R>)",
56            inline=True,
57        )
58        .add_field(
59            "Joined server on",
60            f"<t:{joined_at}:d>\n(<t:{joined_at}:R>)",
61            inline=True,
62        )
63        .add_field(
64            "Roles",
65            ", ".join(r.mention for r in roles),
66            inline=False,
67        )
68    )
69
70    await ctx.respond(embed)
71
72
73def load(bot: lightbulb.BotApp) -> None:
74    bot.add_plugin(info_plugin)

And in bot.py we’ll need to make a little change. On line 17, 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-08-13 17:22:03,151 lightbulb.app: Extension loaded 'extensions.info'

Now let’s go and try out the command:

_static/userinfo_1.png _static/userinfo_2.png

Now to go through what everything does…

  • Line 7 - Create a plugin named Info, which will be used to add our new command
  • Line 10 - Decorator to attach the following command to the plugin

  • Line 11-13 - 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.
  • Line 14 - Decorator to create the command, setting the name to “userinfo” and the description to “Get info on a server member.

  • Line 15 - Converts the decorated function into a prefix command and slash command

  • Line 16 - The command’s function, which takes the parameters ctx and user
  • Line 17 - Get the guild (ctx.get_guild())
  • Line 21-22 - If a user was not passed as an option (user will be None), we assign ctx.author to user
    Then, get the member of the guild
    Note: This will return None if the target is not found in the guild
  • Line 28-29 - 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 31-34 - Get the member’s list of roles, excluding @everyone, then sort them from highest role to lowest

  • Line 37-42 - Make a Discord embed setting the title, description, colour and timestamp

  • Line 43-47 - Set the embed’s footer and thumbnail

  • Line 48-67 - 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 70 - respond to the interaction with the embed (Read the docs - Context.respond)

  • Line 73-74 - the load function, to load the extension when the bot starts
    Note: This is required in each extension

Read the docs - Extensions