So I had this idea with my friends from Discord the other day of making a custom Discord bot for our server and, since I’ve got a VPC in Vultr and the bot itself barely consumes any memory to run, I decided to go and make it myself.

Idea and API

I decided to use the discord.py API, since it is very well documented and has tons of examples. My ideas for this bot for now is fairly simple:

  • Integrate my OpenAI API key to talk to it with a custom prompt to make it sound like Jarvis (yes, the one from Iron Man)
  • React to messages with specific keywords with custom emojis from the server
  • Count how many times @everyone were mentioned by one specific user (he is known for doing that a lot)
  • Some slash commands, like:
    • Store and list funny quotes some members have said
    • List games that we have played and refunded (we do that a lot)

I implemented some other things along the way, but these were the ones that first came to my mind.

Setup

First I had to create my API key and set OAuth2 for my server at the Dev Dashboard. After that, set the both discord and OpenAI keys in an .env file and install discord.py and OpenAI libs and we’re good to go. Here we initialize everything:

 1import discord
 2from dotenv import load_dotenv
 3from openai import OpenAI
 4import os
 5
 6load_dotenv()
 7
 8TOKEN = os.getenv("TOKEN")
 9OPENAI_KEY = os.getenv("OPEN_AI_KEY")
10
11intents = discord.Intents.default()
12intents.message_content = True
13GUILD = discord.Object(id=GUILD_ID)
14
15bot = commands.Bot(command_prefix="!", intents=intents, help_command=None)
16openai_client = OpenAI(api_key=OPENAI_KEY)

The premise here is set your intents, which is basically what are the permissions of the bot, what it can do. I’m defaulting it here. Next we create out guild object, which has my guild id on a constant set before. That’s basically it. Now we are set to implementing our stuff.

Slash commands

Let’s do something simple, the add quote function I talked about earlier. We must use the decorator @bot.tree.command to add a slash command. Then we add the command itself add_quote along with its description and the guild object.

 1@bot.tree.command(
 2    name="add_quote", description="Adiciona uma citação de alguém", guild=GUILD
 3)
 4async def add_quote(
 5    interaction: discord.Interaction, member: discord.Member, quote: str
 6):
 7    with open("data/quotes.json", "r") as file:
 8        quotes = json.load(file)
 9    quotes.append(
10        {
11            "member_id": member.id,
12            "member_name": member.display_name,
13            "quote": quote,
14            "date": datetime.now().strftime("%d/%m/%Y"),
15        }
16    )
17    with open("data/quotes.json", "w") as file:
18        json.dump(quotes, file)
19    await interaction.response.send_message(
20        f'Citação adicionada: "{quote}" - {member.display_name}'
21    )

We then define the function (with async) that gets the member and the quote as input. I then opens a json file with all the quotes and add it. Here, I am using the json lib as well.

Let’s now implement some reactions to messages with certain keywords. First we set a json that we will call KEYWORDS with our set of keywords and respective emojis. Should look something like this:

1{
2  "other": 123456789,
3  "something": 1459574930294444124,
4}

Now we implement the listener:

 1@bot.event
 2async def on_message(message: Message):
 3    if message.author == bot.user:
 4        return
 5
 6    content = message.content.lower()
 7
 8    for keyword, emojis in KEYWORDS.items():
 9        if keyword in content or keyword in message.content:
10            if not isinstance(emojis, list):
11                emojis = [emojis]
12            for emoji in emojis:
13                if isinstance(emoji, int):
14                    emoji = bot.get_emoji(emoji)
15                if emoji:
16                    await message.add_reaction(emoji)

This will listen to every message and react accordingly. We can also add here our LLM integration, which will be called when the bot has been mentioned. I defined a SYSTEM_PROMPT beforehand with some information about the server, its members and a custom funny Jarvis prompt.

 1    if bot.user and bot.user.mentioned_in(message) and not message.mention_everyone:
 2        user_message = message.content.replace(f"<@{bot.user.id}>", "").strip()
 3        if user_message:
 4            try:
 5                response = openai_client.chat.completions.create(
 6                    model="gpt-4o-mini",
 7                    messages=[
 8                        {"role": "system", "content": SYSTEM_PROMPT},
 9                        {"role": "user", "content": user_message},
10                    ],
11                    max_tokens=300,
12                )
13                reply = response.choices[0].message.content
14                finish_reason = response.choices[0].finish_reason
15                print(f"Reply received: {reply}")
16                print(f"Finish reason: {finish_reason}")
17                print(f"Full response: {response.choices[0]}")
18
19                if reply:
20                    await message.reply(reply)
21            except Exception as e:
22                await message.reply("Desculpe, não consegui processar sua mensagem.")
23                print(f"OpenAI error: {e}")

With that, I intend to implement some cooler stuff in the future with custom voice and stuff, but this is ok for now. Checkout the repo if you’d like.