Make a Bot and Client

Learn Hikari & Tanjun!

By Asterisk KatieKat

Brief History of discord.py

A few months ago discord.py1 was the king of Discord Bots when it came to Python. Google "build a discord bot python" and chances are the tutorial would be about Dpy. Recently though Rapptz, the owner of Dpy, announced that he was no longer interested in maintaining the library. Unfortunately, no one he trusted was willing to take the reins of the project.

It's a sad event, we had personally build a bot with Dpy and have been extending it for a few years. We initially started it because popular bots limited reaction roles to 20 roles. We also run a 24-32K discord server and needed much more than that. So our idea, build our own. We have spent roughly 4 years learning Dpy, I would not say we're a master or expert by any stretch, but we've done a lot with it. Over the years more and more features were added. A full moderation suite for warns, strikes, kicks and bans that were tracked through postgres, a full xp and level system with customizable goals and values, a customizable escaperoom game, even a small Kanban planning feature and so much more.

discord.py's closing hit us pretty close to home because of this. We spent many hours making this Bot by ourselves and helping teach some kids programming with it. Rapptz detailed his reasoning very well2, it's difficult to maintain a large open source project. Couple that stress with the gentle reminder that discord.py is not Rapptz day job and he made no money off of it. Discords development process is fast, chaotic and not always well communicated. It does not sound like an incredibly fun experience honestly. So while we are sad to see discord.py go we can only wish Rapptz and the discord.py team the absolute best. They did a wonderful job providing a library for many years.

Time moves on, even if discord.py doesn't. Discord mandated that Slash commands are a required. Rapptz last update sounds pretty steadfast that no further development will be happening, so we need to find an alternative. We briefly considered using some discord.py extensions. There are certainly a few that say they can provide the slash command functionality. We watched in anticipation for roughly a month to see the what would happen. A fork of called pycord did show up with promises to add new features and support. Our concern is the developers who have started this fork appear to be a bit inexperienced. We are thrilled new developers are getting into library support. Especially when it is needed and no one else is willing to! Many of the features in our bot ended up becoming fairly complex. The XP system for example mirrors data between Redis and Postgres and manages every user in a 25K guild. Stability is a major concern for us.

We would like to see continued active development and a good community to seek help from.

We spent a few weeks looking around and considering our options. The library we found and liked the most is Hikari3 and Tanjun4. Hikari is the main library, but unlike discord.py, hikari does not provide any build in "bot" functionality. If you're used to discord.pys cogs, Hikari does not provide this. Hikari does have an active community library that fills these gaps. The two biggest are Lightbulb5 and the previously mentioned Tanjun4. Lightbulb will provide the familiar "plugin" style of OOP classes that most discord.py programmers will be familiar with. Tanjun takes a more functional approach. Instead of classes and inheritance Tanjun provides a number of decorators. Neither Tanjun or Lightbulb are "better" or "more powerful" than the other, it's more a choice of how you prefer to implement your bot. If you are new to programming, I would recommend Lightbulb5.

So what's happening here?

After reviewing Hikari and Tanjun we decided to convert our bot! There's not nearly as many resources for Hikari or Tanjun as there was for discord.py, so we want to help fix that! We plan on writing blog posts here, then streaming the programming on our Twitch6 (you should follow us if you haven't!) and finally posting all the code to our Gitlab repo7 for this project! This post we wanted to get some of the quick meta information out of the way and provide a very basic overview of hikari.

Building the Bot with Hikari

Token and Developer Account

Just like discord.py (or most bots really), you need to get a token first. We're not going to review how to do this because we feel it's been covered to death, but discord.js provides an excellent walkthrough8. Just follow this tutorial through and copy down the Bot Token you create. The tl;dr for Bot Tokens:

  • Tokens allow your code to connect to and communicate with Discord.
  • No Token == Bot won't work.
  • Tokens are unique.
  • Don't share yours, other people can run your Bot account if you do.
  • Tokens can be regenerated at any time! If you do leak yours, you can reset it!
  • Don't store it in GIT. We recommend using environment variables. Later we'll provide a setup too!

Developer Server/Guild

You will also need a Discord Guild to test your bot. You're probably used to calling these "servers", but discord's official documentation refers to them as "guilds". You can make a new Server by clicking the "+" button in your servers list. This will walk you through setting up your own. For now you won't need to worry about any of the customization options like a logo or roles. We just need an empty server.

Now we will need to invite your bot! Jump back to the Discord Developers Panel9, open your bot application, and go to the OAuth2 tab.

Make sure to set the correct permissions!

To the code

The first thing we will need to do is install hikari itself. We recommend using PyEnv10 and it's Virtualenv Plugin11 to help manage your dependencies. Tools like pip, poetry, or pipenv allow you to manage libraries as dependencies. PyEnv manages installing different versions of Python. The virtualenv plugin adds a wrapper for virtualenv/builtin pyenv to create self contained Python projects. While we recommend using these two tools, they are not a hard requirement. All you really need is Python 3.9.5 and pip.

Once you you have the right Python, you will still need to install Hikari. You can do this with the following command:

1
pip install hikari

There is also a "speedups" version of Hikari. This version requires some additional software, notably a C Compiler which is not preinstalled on Windows. You can install the speedups version with the command below, just make sure you have gcc or g++ installed.

1
pip install hikari[speedups]

Next we will create a bot.py with the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import hikari
import os

bot = hikari.GatewayBot(os.environ.get("BOT_TOKEN"))

@bot.listen()
async def ping(event: hikari.GuildMessageCreateEvent) -> None:
    if event.is_bot or not event.content:
        return

    if event.content.startswith("!ping"):
        await event.message.respond("Pong!")

bot.run()

Let's go through this script line-by-line.

Lines 1-2 import both the hikari and os modules.

Line 4 creates a GatewayBot12 using the Token you created. The GatewayBot is hikari's way to provide basic bot functionality. It does not provide any way to register commands, set things like prefixes, or join music channels. It does provide a way to subscribe to events, create listeners, interactive with users and access the Discord Rest API. If you want to work with more abstract concepts like commands or groups you need Tanjun or Lightbulb, which we will be covering next post!

Line 6 might look familiar if you've used discord.py. This does basically the same thing. We are using our previously created GatewayBot to define the next function is a listener. Listeners allow us to assign a callback function to events. But unlike discord.py, we are not passing any information to tell it what "type" of event it should respond to. Should it reply to Messages? A member joining? A member being banned? Hikari actually handles this through it's type hinting system on the next line.

Line 7 defines our new function, ping, and what arguments it should receive. In our case we are saying the function should receive one argument, event of the type GuildMessageCreateEvent. Defining the function this way will also lock the function to only receive these types of events. Hikari has a large list of events12 that you can use in place of GuildMessageCreateEvent, just make sure you understand what they return.

Lines 8-10 are just to ensure we do not respond to any bots or empty messages. Remember, this function run and receive an event for every single message from every guild it is in. That includes messages from other bots or messages that only have embeds with no content. We should filter that to not error.

Lines 11 checks if the message starts with !ping, the command to respond to.

Line 12 uses respond which is hikari's way of sending messages. send() supports a number of options for attachments, embeds, mentions and replys. In this article we are just using the most simple version. Providing just a string defaults to assuming you are sending "content", like so send(content="Pong!").

Finally like 14 starts the bot! Save your file and copy that Bot Token from earlier to your clipboard, you're about to need it. Open your terminal and navigate to where you saved your bot. Run the command below, with your-token-here replaced with your actual bot token.

1
BOT_TOKEN=your-token-here python3 bot.py

Adding BOT_TOKEN=your-token before the python command is a quick way to set a temporary EnvironmentVariable. Until your python script finishes executing, you will be able to access that token via os.environ.get("BOT_TOKEN") in any Python file. This is how we are able to access the token without actually placing it into the file. This also ensure you do not accidentally share your key or save it to a git repo. There are better ways to do this, and we will introduce one later, but for now this is fast and easy!

If you open the server you invited your bot to and run !ping you should get a response!

Once your bot starts up run !ping

Tanjun and the Client

Imagine having to write every bot command as a listener. Yikes. By default hikari doesn't provide much more than this to handle our bot commands. That's where Tanjun comes to the rescue. You can install Tanjun the same way we did hikari:

1
pip install hikari-tanjun

With Tanjun installed we will need to change our bot.py some.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import hikari
import tanjun
import os

def build_bot() -> GatewayBot:
    TOKEN = os.environ.get("BOT_TOKEN")
    bot = hikari.GatewayBot(TOKEN)

    make_client(bot)

    return bot

def make_client(bot: hikari.GatewayBot) -> tanjun.Client:
    client = (
        tanjun.Client.from_gateway_bot(
            bot,
            mention_prefix=True,
            set_global_commands=GUILD_ID
        )
    ).add_prefix("!")

    return client

In lines 3-11 we have completely deleted the previous listener and moved our GatewayBot into it's own function. This might look a little odd if you are used to OOP, but this is the more functional approach Tanjun takes.

Lines 13-22 defines a new make_client function. The function takes a GatewayBot , builds and returns a new tanjun.Client.

Line 18 defines the set_global_commands which accepts a Discord Guild ID. You can get the ID by right clicking your Servers Icon/Logo and selecting " Copy ID". This isn't required immediately, but will be for Slash Commands.

Lastly notice line 20, we are finally adding a bot prefix! By default we set ours to "!", just an exclamation mark. You can change this if you'd like.

Now we will need a way to run the bot again. Let's create a run.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import os

from bot import build_bot

if os.name != "nt":
    import uvloop

    uvloop.install()

if __name__ == "__main__":
    build_bot().run()

Lines 3-8 are to enable hikari's Speedups. If you do not have a working C compiler, or don't want to bother, you can just remove those lines.

Line 11 calls our previously defined build_bot, which as you remember returns a GatewayBot. This allows us to "chain" the functions. Instead of having to set the GatewayBot to it's own variable and call run() off of that, we can do this shorthand.

Making a Tanjun Plugin

So what's the bot do now? Nothing!

Let's work on fixing that!

Make a new folder called plugins and add a new file to that folder __init__.py. That tells python that the folder should be a Python Module and we will be able to import from there. Create another new file in plugins named utilities.py and let's add some functionality:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import tanjun


nl = "\n"

component = tanjun.Component()

@component.with_slash_command
@tanjun.as_slash_command("whats-my-id", "Find out what your User ID is!", default_to_ephemeral=True)
async def whats_my_id(ctx: tanjun.abc.Context) -> None:
    await ctx.respond(f"Hi {ctx.author.mention}! {nl} Your User ID is: ```{ctx.author.id}```")


@tanjun.as_loader
def load(client: tanjun.abc.Client) -> None:
    client.add_component(component.copy())

Line 4 defines a new variable of tanjun.Component(), this is the primary way to add new "commands" to your bot.

Line 6 uses that newly defined component and calls with_slash_command(). This registers the following function as a new Slash Command in your bot.

Line 7 registers the the name, description and ephemeral status of the command. @tanjun.as_slash_command() is how to add metadata to your commands. There are a few more options as well14 that allow you to limit to specific choices and more.

Line 8 starts our actual command. By default all commands must accept a tanjun.abc.Context in some form, but there are subclasses for Message and Slash commands15.

Line 9 tells our bot to respond()16 to the users message with their mention and id.

Finally lines 12-14 are boilerplate. There is no unique code in it, so it can literally be copy/pasted to any plugin. Just make sure you have at least one function defined with @tanjun.as_loader otherwise Tanjun will not be able to load and run your plugin!

Register the Plugin on your bot

Now update your bot.make_client function to load the new module:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def make_client(bot: hikari.GatewayBot) -> tanjun.Client:
    client = (
        tanjun.Client.from_gateway_bot(
            bot,
            mention_prefix=True,
            set_global_commands=GUILD_ID
        )
    ).add_prefix("!")

    client.load_modules("plugins.utilities")

    return client

Run your Slash Command!

Now if you go to your Server and type /whats-my-id you should get a new popup!

Slash command suggestions

This popup will list all Slash Commands available in the server. This dialogue will also give command options and arguments if the command requires those and even validate them! So if your command requires a Role or User you can lock the popup to only accept those as inputs!

NOTE: If you are not seeing your bot's Slash command show up, make sure your clients set_global_commands is correct! Discord bots have to register slash commands with Discord when they start. If your guild id is not correct it can take up to 1 hour to propagate if you do not set this!

The final bot command

If everything is setup right you should get a response like this! Notice the "Only you can see this - Dismiss message" prompt, this indicates that the message is "ephemeral". Only the user who caused the ephemeral message can see it. It's also important to note that unlike old message style commands, other users cannot see your Slash commands when sent. The only indication is the reply shown.

If you would prefer the message be viewable to everyone, simply set default_to_ephemeral=False in plugins.utilities.


Footnotes & Sources


  1. Rapptz, discord.py repository, https://github.com/Rapptz/discord.py 

  2. Rapptz, The Future of discord.py, https://gist.github.com/Rapptz/4a2f62751b9600a31a0d3c78100287f1 

  3. Hikari-py, Hikari, https://github.com/hikari-py/hikari 

  4. FasterSpeeding, hikari-tanjun repository, https://github.com/FasterSpeeding/Tanjun/ 

  5. tandemdude, hikari-lighbulb repository, https://github.com/tandemdude/hikari-lightbulb 

  6. Patchwork Collective, TwitchTV Channel, https://www.twitch.tv/patchworkcollective/ 

  7. Patchwork Collective, hikari-tanjun-tutorial repository, https://gitlab.com/aster.codes/hikari-tanjun-tutorial 

  8. discord.js, Setting up a Bot Application https://discordjs.guide/preparations/setting-up-a-bot-application.html#your-token 

  9. Discord, Discord Developer Account, https://discord.com/developers/applications/889565907200921621/oauth2 

  10. PyEnv, pyenv Repository, https://github.com/pyenv/pyenv 

  11. PyEnv, pyenv-virtualenv Repository, https://github.com/pyenv/pyenv-virtualenv 

  12. Hikari-py, GatewayBot documentation, https://hikari-py.github.io/hikari/hikari/impl/bot.html#hikari.impl.bot.GatewayBot 

  13. Hikari-py, Hikari Events documentation, https://hikari-py.github.io/hikari/hikari/events/index.html 

  14. Tanjun, Tanjun as_slash_command documentation, https://fasterspeeding.github.io/Tanjun/master/tanjun.html#as_slash_command 

  15. Tanjun, Tanjun Context Documentation, https://fasterspeeding.github.io/Tanjun/master/tanjun/abc.html#Context 

  16. Tanjun, Tanjun Context.respond Documentation, https://fasterspeeding.github.io/Tanjun/master/tanjun/abc.html#Context.respond 

We appreciate Donations & Tips!

We would much rather be making cool tutorials, streaming code, and making accessibility tools. Unfortunately we have bills and a job. Hopefully one day, with enough Patrons and donations this can be fulltime!

Authors

Asterisk

Role: Host Apparently Normal Part

Asters's current pfp

Pre-2020 Aster consider themself a very neurotypical male. As the COVID pandemic hit it magnified many issues Aster had been able to hide or compensate for. Since diagnosis Aster now tries to use their previous skills with teaching and speaking to help spread education and awareness about DID/OSDD. In their spare time Aster enjoys programming, teaching, and helping build the Official Game Theory Discord.

>> Learn More about Aster and The Patchwork Collective

Katie is the Social Protector for the Patchwork Collective and the first Part to start communicating with Aster in early 2021. Most of Katie's System Role revolves around handling social situations, work, and dealing with external strangers. Once the internal walls came down Katie quickly found her interests in research, programming, and writing.

>> Learn More about Katie and The Patchwork Collective