The hook
My D&D group has been running the same campaign for two years. We are four sessions from the end. And for the last eight months, a Discord bot has been writing our session recaps.
It is not a serious bot. It writes a short summary of what happened, and then it says something rude about one of us. Two actual lines from the channel:
“Session 41: the party finally reached the Ashen Vault. Torvald picked the lock in one attempt, which everyone agreed was suspicious.”
“Meanwhile Bryn spent the entire session asking the innkeeper about his feelings. Bryn is a barbarian.”
That is the whole thing. It reads the session’s chat log, writes a paragraph, adds one insult, and posts it. My group checks for it before they check the actual notes. Bryn has asked me twice to turn it off.
The reason I am writing this up is not the bot. It is that building it cost me nothing for the first two months and about the price of a sandwich a month after that. If you have been assuming this kind of project needs a subscription, it does not, and the gap between “assuming” and “trying” is roughly one evening.
Getting a key without paying anything
You need an API key. You do not need to pay for one to start.
Every major provider runs a free tier, and for a personal project the free tier is not a teaser. It is genuinely enough. My bot handles one session a week, which is one longish request every seven days. That is nowhere near any free limit I have ever hit.
If you have never done this before, this walkthrough on how to get a Gemini API key covers both the free tier and the paid one in about five minutes. The free tier was more than enough for my bot’s first two months, and I only moved off it because I added a second feature, not because I ran out.
Two practical warnings, learned the tedious way.
Do not put the key in your code. Put it in an environment variable. I know you know this. I also know you are going to paste it into the file “just for testing”, and then push it, and then get an email about it.
And check the free tier’s terms before you build something you care about. Free tiers sometimes come with different data handling than paid ones. For a bot that reads my friends’ jokes about a fictional innkeeper, I do not care. For anything involving real personal information, read the page.

Illustration: a hobby developer keeping an AI API credential separate from the tabletop-game bot codebase.
The 40 lines that actually do the work
Here is the core of it. I am using discord.py, which has genuinely good documentation, and an OpenAI-compatible client for the model call.
import os, discord
from openai import OpenAI
client = OpenAI(
base_url=os.environ[“AI_BASE_URL”],
api_key=os.environ[“AI_API_KEY”],
)
MODEL = os.environ.get(“AI_MODEL”, “gemini-3.1-flash”)
SYSTEM = (
“You write short D&D session recaps. Two paragraphs maximum. “
“End with exactly one gentle joke about one player. “
“Never be cruel and never mention anything outside the log.”
)
bot = discord.Client(intents=discord.Intents(messages=True, message_content=True, guilds=True))
@bot.event
async def on_message(msg):
if msg.author == bot.user or not msg.content.startswith(“!recap”):
return
log = [m.content async for m in msg.channel.history(limit=300)]
log.reverse()
resp = client.chat.completions.create(
model=MODEL,
messages=[
{“role”: “system”, “content”: SYSTEM},
{“role”: “user”, “content”: “Session log:\n” + “\n”.join(log)},
],
)
await msg.channel.send(resp.choices[0].message.content)
bot.run(os.environ[“DISCORD_TOKEN”])
In plain English, top to bottom. Set up a client that can talk to a model. Write down the personality once, in the system prompt, so you are not repeating yourself. Wait for someone to type !recap. Grab the last 300 messages. Reverse them, because Discord hands them to you newest first and a recap written backwards is confusing in a way that took me an embarrassingly long time to notice. Send the whole thing to the model. Post whatever comes back.
That is it. The insults are not code. They are one sentence in the system prompt.
One line in that snippet is doing more work than it looks. Because I call the model through an aggregator rather than a single vendor’s SDK, I can swap in a different model without rewriting any code by changing the AI_MODEL environment variable. I have changed models three times since I started, twice to save money and once because a newer one was funnier, and the bot never noticed. If I had wired one vendor’s library directly into this file, each of those swaps would have been an evening of work instead of a restart.

Illustration: a Discord recap bot reads the chat, calls one compatible client, routes to a swappable model, and posts the recap.
Keeping it cheap once people actually use it
Eventually you hit the free tier ceiling. For me it took about eight weeks, and only because I added a feature that summarises the group chat during the week as well.
It is worth knowing in advance what Gemini costs once you pass the free tier, because for a hobby project the answer is usually less than one takeaway coffee a month, and people abandon projects at this step for no reason at all. They see the word “pricing”, assume it means enterprise money, and close the tab.
Four things that keep the bill small, roughly in order of how much they help.
Use a cheaper model. This is most of it. The lightweight tiers are a fraction of the flagship price and they are perfectly capable of writing a funny paragraph. My bot has never needed a frontier model, and honestly the cheap ones are funnier because they are less careful.
Trim your context. I was sending 300 messages. Most sessions only need the last 150, and I halved my token use by changing one number.
Cache what repeats. If part of your prompt is identical every time, most providers will bill cached input at a large discount. My system prompt never changes, so it is nearly free.
Set a hard spend limit in the dashboard. Do this on day one. Not because you will spend a lot, but because a bug in a loop can turn a cheap project into an expensive one in about four minutes.

Illustration: four cost controls for a hobby AI bot—a lighter model, shorter context, cached prompts, and a hard spend limit.
Four more weekend projects to steal
A watchlist generator. Feed it the last twenty things you watched and let it argue for what is next. Mine keeps recommending Danish crime drama, which is not wrong, but it is a lot.
A comics plot summariser. Point it at a synopsis of a run you have not read and get a “what you need to know before issue one” briefing. Useful for anyone who has ever tried to start a long-running series.
A Twitch chat sentiment monitor. Track when your chat is genuinely excited versus politely watching. Streamers I have shown this to find the timeline more interesting than the score.
A TTRPG NPC generator. Not a stat block, a person. A name, one want, one secret, one verbal tic. This is the one my group actually asks for, because inventing a fifth shopkeeper on the spot is the hardest part of running a game.
Each one is the same shape as the bot above. Read something, send it to a model with a personality attached, post the result.
The obligatory don’t-be-a-jerk section
I would rather write this myself than have someone else write it about me.
Do not monetise fan work that generates other people’s characters, art, or writing. A bot that roasts my friends is fine. Selling AI-generated art of a franchise you do not own is not, and the fact that it is technically easy has nothing to do with whether it is fair.
Label what is generated. My channel says the recaps are written by a bot. Nobody in my group would have been upset if they found out later, but plenty of people would be, and telling them costs one sentence.
And do not use any of this to avoid paying someone who should be paid. If you need art for a project, commission an artist. If you need a logo, hire a designer. If you need a session recap that gently insults a barbarian, fine, write a bot. The line is not complicated, and most of the anger aimed at this technology comes from people watching it get crossed by companies who knew exactly what they were doing.
Build the silly thing. Keep it silly.






