Sitemap

Making A Discord Bot That Uses GPT and Dalle-2

17 min readApr 27, 2023

In the first half of this article, I’ll be sharing a summary/tutorial on building a multi-feature AI-powered Discord bot, which I have created as an open-source project. If you’re interested in using the bot without diving into the coding aspects, feel free to skip to the second part of the article, where I’ll walk you through setting up the bot for your own use, using the existing GitHub repository: https://github.com/AmirMEdris/Discordbot.

With rapid advancements in AI, creating applications that leverage AI to perform interesting tasks has become more accessible than ever. As a Discord user, I decided to build a Discord bot that my friends and I could enjoy like roasting users and summarizing conversations. I learned a lot from this and wanted to share what I learned.

Part 1 — Making A Discord Bot:

Building the AI-Powered Discord Bot:

In this section, we’ll walk through the process of building our Discord bot and implementing the roast and summarize features.

To get started I’m going to first walk you through the process of getting a discord bot online. It requires you to do a few simple things on the discord developer url: https://discord.com/developers/applications.

After you have logged in create a new application using the button on the top right of the screen

Press enter or click to view image in full size

Upon naming your application, agreeing to the discord ToS, and clicking accept you should be brought to a page for your new application

Press enter or click to view image in full size
Press enter or click to view image in full size

Once on your applications page click the bot tab on the left and generate a token. This token will be what our code uses to communicate with discord api and carry out actions as our bot. Note don’t share this token and if you plan on using github you should add it to a config file that is included in your .gitignore. If the token is in your code and you push it others could get it and use your bot. Discord detected that my bot token was in a my github repository and they immediately gave me a new token. As far as I know tokens and api keys should not just be in your code.

Instead of that I made a config.yaml file which holds both my bot token and openai api key, also any other api keys or sensitive information I might need to add later. Yaml is an alternative to json and I chose to use it because it’s more human readable but you could also use a json.

Assuming you have your bot token its time to set things up on our end. Navigate to your environment of choice(I use pycharm but it shouldn’t matter so long as you know how to use pip and the file you plan to work in)

The following libraries are needed so go ahead and install them if you dont have them already.

pip install discord.py
pip install openai
pip install PyYAML

After installing we are going to make two files in our project directory config.yaml and .gitignore.

Given that you are in your projects directory (The file you plan to work in) you can do this using the one of the following based on what command line you are using.

Bash (Unix-based systems) :

touch config.yaml
touch .gitignore

PowerShell (Windows):

New-Item -ItemType File -Name "config.yaml"
New-Item -ItemType File -Name ".gitignore"

Command Prompt (Windows):

type nul > config.yaml
type nul > .gitignore

Once those files are made add config.yaml to the gitignore by opening it and typing config.yaml. This will make github ignore this files existence so if someone wishes to clone your repo they will have to make their own in order for the bot to work for said person.

Now in the yaml file you will want to include an openai api key and the bot token. It should look like this.

discord_bot_token: "YOUR_DISCORD_BOT_TOKEN"
openai_api_key: "YOUR_OPENAI_API_KEY"

Now we can get started on the code. Make a python script, I named mine bot.py, this will act as the main script.

Originally I had all the code for the bot in one file but it started to become hectic and I split it up using cogs which I’ll talk more about later but essentially our “bot.py” is going to do a few things. First it will load in our tokens then it will add our commands to our bot using Cogs and importing other python files. Then finally it will start the bot.

When this file runs, if we got to a discord server that has the bot in it, we should see it go online.

import openai
import discord
from discord.ext import commands
import yaml

# Read config.yaml and get the openai_key and bot_token
with open("config.yaml", "r") as file:
config_data = yaml.safe_load(file)

# Set OpenAI API key
openai.api_key = config_data.get("openai_key")
bot_token = config_data.get("bot_token")

# Permissions we need
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
intents.members = True

# Declaring that our bot is named bot and that its command is !!
# It also gives the intents we set in the section above
bot = commands.Bot(command_prefix="!!", intents=intents)

@bot.event
async def on_ready():
print(f"{bot.user.name} is online.")

@bot.command(name="test")
async def test(ctx):
await ctx.send("Hello, this is a test command!")

bot.run(bot_token)

If you are used to Python but not discord there are likely a few keywords and commands in this that may be unfamiliar to you. I'm going to take this opportunity to give you a break down of the discord features used here.

Intents are a mechanism provided by Discord to specify the events and data your bot can access, allowing it to receive and process specific information from the server. In our case, we have enabled intents for messages, message content, and members. I believe the correlate to the permissions when you generate the url to add the bot to your server in the oauth tab on your bots developer page.

The async and await keywords are used in Python for asynchronous programming. They enable our bot to perform multiple tasks simultaneously, such as listening for events and responding to commands, without blocking other operations.

@bot.event is a decorator that registers an event handler for your bot. In our example, we use it for the on_ready event, which is triggered when the bot successfully connects to Discord and is ready to start processing events. By defining an async def on_ready() function and decorating it with @bot.event, we can execute code when the bot is online, such as printing a status message.

@bot.command is another decorator that registers a command handler for your bot. Commands are user-triggered actions that the bot can respond to. By defining an async def function for a command and decorating it with @bot.command, we create a custom command that users can invoke in the server using the bot's command prefix (in our case, "!!"). For example, @bot.command(name="test") registers a command called "test" that, when called as !!test, will execute the defined function and send a response to the server.

Before we move on to implementing the roast feature, let’s create a custom converter class that helps in converting a user’s display name to a Member object. This is useful when you want to perform actions on a specific user or retrieve information about them based on their display name in the server.

To do this, we’ll create a class called ‘DisplayNameMemberConverter’ that inherits from the commands.MemberConverter class provided by the discord.py library. Inheritance in Python is a way to create a new class that is a modified version of an existing class. The new class (child class) inherits the attributes and behavior methods of the existing class (parent class), allowing you to reuse the code and add or override the methods as needed.

Before we dive into the code, let’s discuss why we need the DisplayNameMemberConverter class. When implementing the roast command, we want users to be able to mention someone in the server by their display name. However, by default, the commands.MemberConverter class provided by the discord.py library only accepts user IDs or user tags (e.g., UserName#1234) as valid arguments. To enhance the user experience and allow users to mention others by their display name, we'll create a custom converter class that extends the functionality of the commands.MemberConverter class.

Inheritance is a powerful concept in object-oriented programming that allows one class (the child class) to inherit the attributes and methods of another class (the parent class). This way, you can create new classes by reusing and extending the code from existing classes, which promotes modularity and maintainability. In this case, we create a DisplayNameMemberConverter class that inherits from the commands.MemberConverter class.

class DisplayNameMemberConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
for member in ctx.guild.members:
if member.display_name.lower() == argument.lower():
return member
raise commands.MemberNotFound(argument)

In this code, we override the convert() method of the commands.MemberConverter class. Instead of attempting to convert the argument using the superclass's convert() method, we directly iterate through all the members in the server and check if any of them have a matching display name. If a member is found, the method returns the corresponding Member object.

The raise keyword in Python is used to raise an exception when a specific condition is not met. In our case, if the DisplayNameMemberConverter class fails to find a member with the provided display name, it raises the commands.MemberNotFound exception, including the argument as a parameter. This allows the exception handler to provide a helpful error message to the user, informing them that the mentioned user was not found.

By using inheritance and overriding the convert() method, we can create a custom converter that accepts display names as valid arguments, improving the user experience when interacting with our roast command.

The complete code should now look like this:

import openai
import discord
from discord.ext import commands
import yaml

# Read config.yaml and get the openai_key and bot_token
with open("config.yaml", "r") as file:
config_data = yaml.safe_load(file)

# Set OpenAI API key
openai.api_key = config_data.get("openai_key")
bot_token = config_data.get("bot_token")

# Permissions we need
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
intents.members = True

class DisplayNameMemberConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
for member in ctx.guild.members:
if member.display_name.lower() == argument.lower():
return member
raise commands.MemberNotFound(argument)

# Declaring that our bot is named bot and that its command is !!
# It also gives the intents we set in the section above
bot = commands.Bot(command_prefix="!!", intents=intents)

@bot.event
async def on_ready():
print(f"{bot.user.name} is online.")

@bot.command(name="test")
async def test(ctx):
await ctx.send("Hello, this is a test command!")

bot.run(bot_token)

Implementing the Roast Feature:

Before implementing the roast feature, we need a custom converter class that converts a user’s display name to a Member object. This is useful when performing actions or retrieving information about a user based on their display name in the server. We’ll create a class called DisplayNameMemberConverter that inherits from the commands.MemberConverter class provided by the discord.py library.

Inheritance in Python allows creating a new class that is a modified version of an existing class. The new class (child class) inherits the attributes and behavior methods of the existing class (parent class), enabling code reuse and method additions or overrides as needed.

We need the DisplayNameMemberConverter class for the roast command, which allows users to mention someone in the server by their display name. By default, the commands.MemberConverter class only accepts user IDs or user tags (e.g., UserName#1234) as valid arguments. To improve user experience, our custom converter class will extend the functionality of the commands.MemberConverter class.

In the DisplayNameMemberConverter class, we inherit from commands.MemberConverter and override the convert() method. Instead of using the superclass's convert() method, we iterate through all the server members and check for matching display names. If a member is found, the method returns the corresponding Member object.

If the DisplayNameMemberConverter class fails to find a member with the provided display name, it raises the commands.MemberNotFound exception, including the argument as a parameter. This allows the exception handler to provide a helpful error message to the user.

By using inheritance and overriding the convert() method, we create a custom converter that accepts display names as valid arguments, enhancing user experience when interacting with our roast command.

Here is the updated code:

import openai
import discord
from discord.ext import commands
import yaml

# Read config.yaml and get the openai_key and bot_token
with open("config.yaml", "r") as file:
config_data = yaml.safe_load(file)

# Set OpenAI API key
openai.api_key = config_data.get("openai_key")
bot_token = config_data.get("bot_token")

# Permissions we need
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
intents.members = True
class DisplayNameMemberConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
for member in ctx.guild.members:
if member.display_name.lower() == argument.lower():
return member
raise commands.MemberNotFound(argument)
# Declaring that our bot is named bot and that its command is !!
# It also gives the intents we set in the section above
bot = commands.Bot(command_prefix="!!", intents=intents)
@bot.event
async def on_ready():
print(f"{bot.user.name} is online.")

@bot.command(name="test")
async def test(ctx):
await ctx.send("Hello, this is a test command!")

bot.run(bot_token)

Now that we have a basic understanding of how a Discord bot works, let’s dive into our first feature: the roast command. The goal of the roast command is to generate a funny roast based on a user’s message history, providing a lighthearted way to interact with friends on the server. To accomplish this, we’ll make use of OpenAI’s GPT-3 model to generate a contextually relevant roast.

To create the roast command, we first need a to define an async helper function called. I called it generate_roast(), which takes two parameters: the user's name and a list of messages. The idea is that a user is going to call our function with !!whateverfunctionname and give it a users name. When that happens we will get the username and the list of messages in his history, at that point we can just pass that into generate_roast() and it will return the roast and our main function can make the bot send that as a message. This function constructs a prompt for GPT-3 based on the user's message history and generates a roast using OpenAI's API. The roast is then returned to the calling function.

So our helper function can look like this

async def generate_roast(user_name, messages):
prompt = f"Based on these messages from {user_name}, create a funny roast remember to exclude any " \
f"names mention and focus on topics:\n{messages}\nRoast:"
response = openai.Completion.create(
engine="text-davinci-003",
prompt=prompt,
max_tokens=150,
n=1,
stop=None,
temperature=0.7,
)
joke = response.choices[0].text.strip()
return joke

Now we are going to make the main function which as I said earlier will execute when a user in discord calls it and gives it a name. We define the roast command itself using the @bot.command decorator telling the bot that this is what it should do when it sees that command in chat. We create an async function called roast() that takes a user_name parameter, converts it to a user object, and retrieves the user's message history. Now that we have both the users name and their message history we can generate a roast with our helper function, which is sent back to the server as a message.

Here’s how our bots complete code with both functions.

import discord
import openai
from discord.ext import commands
import yaml

# Read config.yaml and get the openai_key and bot_token
with open("config.yaml", "r") as file:
config_data = yaml.safe_load(file)

# Set OpenAI API key
openai.api_key = config_data.get("openai_key")
bot_token = config_data.get("bot_token")
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
intents.members = True

class DisplayNameMemberConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
for member in ctx.guild.members:
if member.display_name.lower() == argument.lower():
return member

bot = commands.Bot(command_prefix="!!", intents=intents, help_command=None)

async def generate_roast(user_name, messages):
prompt = f"Based on these messages from {user_name}, create a funny roast remember to exclude any " \
f"names mention and focus on topics:\n{messages}\nRoast:"
response = openai.Completion.create(
engine="text-davinci-003",
prompt=prompt,
max_tokens=150,
n=1,
stop=None,
temperature=0.7,
)
joke = response.choices[0].text.strip()
return joke

@bot.command(name='roast')
async def roast(ctx, *, user_name: str):
try:
user = await DisplayNameMemberConverter().convert(ctx, user_name)
except commands.MemberNotFound:
await ctx.send(f"User {user_name} not found.")
return
messages = []
async for message in ctx.channel.history(limit=1000):
if message.author == user:
messages.append(message.content)
if not messages:
await ctx.send(f"I couldn't find any messages from {user.mention}.")
return
# Combine last 40 messages or less
messages = "\n".join(messages[:40])
joke = await generate_roast(user_name, messages)
await ctx.send(joke)

@bot.event
async def on_ready():
print(f"{bot.user.name} is online.")
bot.run(bot_token)

When I initially did this I realized that the code could look very congested and if you want to make a bot with 5,10,15 or more commands this file will be unmanageable. The discord library has a command in it called Cogs, which are a way to modularize and organize your code, making it easier to manage and maintain as the number of commands and features grow. The idea behind cogs is to separate your bot’s functionalities into distinct Python files, each responsible for a specific command or feature, and then import them into your main script.

To implement cogs, you create a class for each feature or command and inherit from the commands.Cog class provided by the discord.py library. Within this class, you can define all the necessary functions and command handlers related to the specific functionality. Once your class is set up, you can then import it into your main script and add it to your bot using the add_cog() method. This method takes an instance of the cog class and associates it with the bot.

Using cogs not only keeps your code clean and organized but also promotes reusability and easier debugging. By having separate files for each functionality, you can quickly locate issues and make updates without having to sift through a cluttered, monolithic script.

Before we continue, let’s organize our code by moving the DisplayNameMemberConverter class to its own file and creating a separate file for the roast functionality. This will make the code more modular and maintainable.

First, create a ‘modules’ folder in your project directory. This folder will store individual Python files for each function or feature, such as the roast command and our DisplayNameMemberConverter class.

To move the DisplayNameMemberConverter to its own file, create a new Python file called 'member_converter.py' within the 'modules' folder. Cut the DisplayNameMemberConverter class from your main code and paste it into this new file.

modules/DisplayNameMemberConverter should now look like this

from discord.ext import commands


class DisplayNameMemberConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
for member in ctx.guild.members:
if member.display_name.lower() == argument.lower():
return member
raise commands.MemberNotFound(argument)

Next, let’s separate the roast functionality. Create a new Python file called ‘roast.py’ within the ‘modules’ folder. To use the class in our other file we will have to import it. In this file, we’ll create a class called ‘Roast’ that inherits from the commands.Cog class provided by the discord.py library:

from discord.ext import commands
import openai
from modules.membercvtr import DisplayNameMemberConverter

class Roast(commands.Cog):
def __init__(self, bot):
self.bot = bot

Now, transfer the generate_roast() function and the roast() method into the 'Roast' class. Ensure you add the @commands.command(name='roast') decorator for the roast() method within the class.

import discord
from discord.ext import commands
import openai
import datetime

from modules.membercvtr import DisplayNameMemberConverter


class Roast(commands.Cog):
def __init__(self, bot, openai_api_key):
self.bot = bot
self.openai_api_key = openai_api_key
print("Roast cog initialized")

async def generate_roast(self, user_name, messages):
prompt = f"Based on these messages from {user_name}, create a funny roast remember to exclude any " \
f"names mention and focus on topics:\n{messages}\nRoast:"

response = openai.Completion.create(
engine="text-davinci-003",
prompt=prompt,
max_tokens=150,
n=1,
stop=None,
temperature=0.7,
)

joke = response.choices[0].text.strip()
return joke

class DisplayNameMemberConverter(commands.MemberConverter):
async def convert(self, ctx, argument):
for member in ctx.guild.members:
if member.display_name.lower() == argument.lower():
return member
raise commands.MemberNotFound(argument)

@commands.command(name='roast')
async def roast(self, ctx, *, user_name: str):
try:
# await ctx.send([member.display_name for member in ctx.guild.members])
user = await DisplayNameMemberConverter().convert(ctx, user_name)

except commands.MemberNotFound:
await ctx.send(f"User {user_name} not found.")
return

messages = []
async for message in ctx.channel.history(limit=1000):
if message.author == user:
messages.append(message.content)

if not messages:
await ctx.send(f"I couldn't find any messages from {user.mention}.")
return

# Combine last 40 messages or less
messages = "\n".join(messages[:40])

joke = await self.generate_roast(user_name, messages)
await ctx.send(joke)

Don’t forget to update any references to the bot, such as bot.command or bot.event, to commands.Cog.command or commands.Cog.event, respectively.

Lastly, let’s edit the ‘bot.py’ file to import the ‘Roast’ cog and add it to the bot. First, add the following import statement at the top of the ‘bot.py’ file:

from modules.roast import Roast

Then, add the following line after initializing the bot instance to add the ‘Roast’ cog:

bot.add_cog(Roast(bot))

bot.py should now look like this:

import openai
import discord
from modules.summary import Summary
from discord.ext import commands
from modules.nickname import Nickname
from modules.visualize import Visualize
from modules.roast import Roast
from modules.help import Help
import yaml

# Read config.yaml and get the openai_key and bot_token
with open("config.yaml", "r") as file:
config_data = yaml.safe_load(file)

# Set OpenAI API key
openai.api_key = config_data.get("openai_key")
bot_token = config_data.get("bot_token")

intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
intents.members = True

bot = commands.Bot(command_prefix="!!", intents=intents, help_command=None)

@bot.event
async def on_ready():
print(f"{bot.user.name} is online.")
await bot.add_cog(Summary(bot, openai.api_key))


bot.run(bot_token)

This was a lot of explaining but if you got this far you should be able to repeat this process for whatever other functionality you might want. And you can repeat this to make as many functions as you want by adding more cogs in the same way.

Part 2 — How to download and use the bot.

Lets start off by taking a look at what the bot can currently do

Remember that the quality of its roast depends greatly on your previous messages, also gpt3 gets confused when seeing names in messages i’ve gotten it to roast itself.

it ignored it here but thats because my last 40 is what is being used so if I spam it enough…

It actually didn’t do it here but you can see how the quality of the joke is much worse. Since this post already went on longer then I thought and I think its unnecessary to do this for every function I have Im just gonna show off some of the other functions I made.

Press enter or click to view image in full size

If you want to use this bot on your own server I encourage you to remember this bot takes uses messages people have sent in discord and many may view prompts generated that include names or events as creepy and/or a breach of privacy. Also remember that using openai isn't free, If someone just spams the visualize function they could rack up a large bill quickly. I set a monthly limit of 5 dollars personally. Finally gpt filters content that's against openai’s code of conduct but sometimes it still produces a roast that goes pretty far.

Press enter or click to view image in full size

Try to use this with people you know that will find it funny. Understanbly some people do not take kindly to a bot reading their messages to make jokes about them.

To download this bot you can go to github and clone my repo:

As of right now you need python installed and the discord.py, openai, PyYAML libraries installed.

Once you cloned the repo make a file called config.yaml in the main folder. Edit as text put your keys in it like so

bot_token: "YOUR_DISCORD_BOT_TOKEN"
openai_api_key: "YOUR_OPENAI_API_KEY"

Replace your keys and save it.

Finally run the bot.py file in python and while that file runs any server that you have your bot in should go online and be responsive. If you generate text or images against openai guidelines you will get an error from python and the bot will keep running.

If you don't have a discord bot token I talked about how to get one at the beginning of Part 1 and you can follow these steps if you don't have an OpenAi key.

Go to the OpenAI website: https://www.openai.com/

  • Click on “Sign Up” in the top right corner of the page, and create an account using your email address, a password, and agreeing to the terms of service.
  • Once you have signed up and logged in, navigate to the OpenAI Dashboard: https://platform.openai.com/signup
  • In the dashboard, you may see different API plans available. Select the appropriate plan for your needs (Free trial, Pay-as-you-go, or a custom plan for enterprises).
  • After subscribing to a plan, you will receive an API key. You can access your API key anytime from your dashboard under the “API Keys” section.

After that you should be good to go. If you got this far I hope you either learned something or got a good laugh from one of your friends. Like I said I plan on adding more features so if you enjoyed please follow me on medium. Finally let me know if you have any ideas or notice any problems.

Thanks for reading.

--

--

Amir Edris
Amir Edris

Written by Amir Edris

Data Scientist/ Machine learning engineer who loves to share about this fascinating field

Responses (4)