🤖Discord Bot Programmed ... in Discord!

Randall - Apr 28 '23 - - Dev Community

Meet my bot Kiryu. His specialty is downloading JavaScript code from a Discord channel and executing it. To have Kiryu automatically add a role to new members who join my server, I write this message in the code channel:

Image description

Kiryu sees the message, extracts the JavaScript code, and eval()s it. Now whenever a new member joins, he will give them a role.

Here's another one. This one makes Kiryu automatically ban anyone who sends a message including a banned word:

Image description

Kiryu re-evaluates code when he starts, when a new message is created in the code channel, when a code message is updated, and when a code message is deleted, so his code always stays in sync with the code I have written in the code channel.

Source Code

Check out Kiryu's core code here. It's just one source file, less than 200 lines.

Run Your Own Instance

If you want to try running your own instance, I pushed an image to Docker Hub. You can easily launch an instance using Docker:

docker run \
  --name kiryu \
  --restart unless-stopped \
  -e PROGRAMMER_IDS='YOUR USER ID HERE' \
  -e BOT_TOKEN='YOUR BOT TOKEN HERE' \
  -e CODE_CHANNEL_IDS='YOUR CODE CHANNEL ID HERE' \
  -e LOG_CHANNEL_ID='YOUR LOG CHANNEL ID HERE' \
  -d \
  mistval/kiryu
Enter fullscreen mode Exit fullscreen mode

If you don't know how to create a bot account, check out the start of this great article and follow the instructions up until you get a bot token, which you can then substitute into the command above, along with your user ID, the ID of the channel you want to write code in, and the channel where you want errors to be reported to.

Once the bot is online, try writing this in your code channel, surrounded by js code tags:

messageHandlers.push(
  (msg) => {
    if (msg.content === 'hello') {
      return msg.channel.createMessage('world');
    }
  },
);
Enter fullscreen mode Exit fullscreen mode

Image description

The bot should react with a green checkmark. Then if you say hello (in a different channel), the bot should respond with world.

Handling Events

As you may have noticed, Kiryu has a built-in messageHandlers array as a convenience. Any function added to that array will be called for every new message that isn't a code message and isn't from a bot. Errors thrown from inside of message handler functions will be automatically reported to the log channel.

For other types of events, you can subscribe to them directly on the bot object, as shown in the very first image in this article when I subscribe to the guildMemberAdd event.

You can see the available events in the eris documentation here. Eris is a Discord bot library for Node.js. It's no longer well-maintained and it has never been as popular as the discord.js library, it's just what I'm personally used to, since I like it for performance reasons.

One gotcha here is that you have to be careful of double-subscribing to events, since the code in the code message may be evaluated multiple times. That's why I run bot.removeAllListeners('guildMemberAdd'); before subscribing to the event in the earlier example.

Module Loader

Kiryu can automatically download and install modules from npm. For one of Kiryu's fancier features, I use the @pagerduty/pdjs module. It's a library for interfacing with the PagerDuty API. Using Kiryu, moderators in my server have a way to open a PagerDuty incident, which will call my cell phone and wake me up from my beauty sleep. I have this feature because I have another bot in 30,000+ servers, and if something goes really wrong with it, I want to know ASAP!

The code for that feature is a bit longer, so I won't show it here, but here's a short example of using the hello-world-npm module to re-implement the above hello world command:

Image description

If Kiryu doesn't have that module downloaded already, he'll download it and exit. When he restarts (which he will do automatically, if you use the Docker command above) he will have the module available. (I couldn't find a way to load a new module into a running process)

Other Examples

If you want to see all the code that I'm running on Kiryu, feel free to check my server where I have the aforementioned PagerDuty command plus a few more, plus SQLite integration.

Security

I would be remiss if I didn't mention that downloading and eval()ing code from a Discord server isn't exactly an inherently secure thing to do. Although Kiryu is written to only evaluate code in messages sent by users whose IDs you specify in the PROGRAMMER_IDS environment variable, bugs in Kiryu or in Discord itself could lead to malicious code execution on the host machine.

Conclusion

So why am I coding a bot like this? Well, just because it's fun, and it's a cool thing to show off in my server. I wouldn't really recommend doing this for a major production bot, but it gets the job done for little chores in my server, and looks cool while doing it! If you're interested in trying Kiryu, or this technique more generally, I'd love to hear what you think in the comments!

. . . . . . . . . . . . . . . . . . . . . . .