Every dev Discord server needs a code roast bot. Someone pastes a code snippet, the bot tears it apart with the energy of a senior developer who hasnβt had coffee yet. Itβs educational, itβs entertaining, and itβs surprisingly easy to build.
In this tutorial, weβll build a Discord bot that watches for code blocks in messages, reviews them with AI in a roast-style tone, and replies with brutally honest feedback. Think code review meets comedy.
What weβre building
User:
!roast
```javascript
function getUser(id) {
var user = null;
fetch('/api/users/' + id).then(res => {
user = res.json();
});
return user;
}
Bot: π₯ CODE ROAST π₯
Oh no. Oh no no no.
-
Youβre returning
userbefore the fetch completes. This function returnsnullevery single time. Youβve built a function whose only job is to return null. Impressive. -
varin 2026? Did you find this code in a time capsule? -
res.json()returns a Promise too, which youβre not awaiting. So even if the timing worked,userwould be a Promise object, not actual data.
Hereβs what you probably meant:
async function getUser(id) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
Rating: 2/10 β The indentation was nice though. π
## Prerequisites
- Node.js 20+
- A Discord account
- An Anthropic API key
- A Discord server where you have admin permissions
## Step 1: Create the Discord bot
1. Go to [discord.com/developers/applications](https://discord.com/developers/applications)
2. Click "New Application" β name it "Code Roaster"
3. Go to "Bot" tab β click "Add Bot"
4. Copy the bot token
5. Under "Privileged Gateway Intents," enable **Message Content Intent**
6. Go to "OAuth2" β "URL Generator" β select `bot` scope and `Send Messages` + `Read Message History` permissions
7. Copy the generated URL and open it to invite the bot to your server
## Step 2: Set up the project
```bash
mkdir roast-bot && cd roast-bot
npm init -y
npm install discord.js @anthropic-ai/sdk
Step 3: Build the bot
Create index.js:
import { Client, GatewayIntentBits } from 'discord.js';
import Anthropic from '@anthropic-ai/sdk';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
const anthropic = new Anthropic();
const ROAST_PROMPT = `You are a code review bot with the personality of a brutally honest senior developer who's seen too much bad code. Your job is to roast the code β point out every issue in a funny, sarcastic way. But always be educational: explain WHY something is wrong and show the fix.
Rules:
- Be funny and sarcastic, but never mean-spirited or personal
- Always explain the actual technical issue behind each roast
- End with a corrected version of the code if possible
- Give a rating out of 10
- Keep it under 300 words
- Use emojis sparingly for effect
Roast this code:`;
// Listen for !roast command
client.on('messageCreate', async (message) => {
if (message.author.bot) return;
if (!message.content.startsWith('!roast')) return;
// Extract code block
const codeMatch = message.content.match(/```(\w*)\n?([\s\S]*?)```/);
if (!codeMatch) {
message.reply('Paste a code block with your message. Example:\n!roast\n\\`\\`\\`js\nyour code here\n\\`\\`\\`');
return;
}
const language = codeMatch[1] || 'unknown';
const code = codeMatch[2].trim();
if (code.length < 10) {
message.reply("That's barely code. Give me something to work with. π€");
return;
}
if (code.length > 3000) {
message.reply("I'm not reading all that. Keep it under 100 lines. π");
return;
}
// Show typing indicator
message.channel.sendTyping();
try {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 800,
messages: [{
role: 'user',
content: `${ROAST_PROMPT}\n\nLanguage: ${language}\n\`\`\`${language}\n${code}\n\`\`\``
}],
});
const roast = response.content[0].text;
// Discord has a 2000 char limit
if (roast.length > 1900) {
const parts = roast.match(/[\s\S]{1,1900}/g) || [];
for (const part of parts) {
await message.reply(part);
}
} else {
message.reply(`π₯ **CODE ROAST** π₯\n\n${roast}`);
}
} catch (err) {
console.error(err);
message.reply("My roasting circuits overheated. Try again. π« ");
}
});
// Also support !review for a nicer tone
client.on('messageCreate', async (message) => {
if (message.author.bot) return;
if (!message.content.startsWith('!review')) return;
const codeMatch = message.content.match(/```(\w*)\n?([\s\S]*?)```/);
if (!codeMatch) {
message.reply('Paste a code block with your message.');
return;
}
message.channel.sendTyping();
try {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 800,
messages: [{
role: 'user',
content: `Review this code constructively. Point out bugs, improvements, and best practices. Be helpful and professional. Keep it concise.\n\nLanguage: ${codeMatch[1] || 'unknown'}\n\`\`\`\n${codeMatch[2].trim()}\n\`\`\``
}],
});
message.reply(`π **Code Review**\n\n${response.content[0].text}`);
} catch (err) {
message.reply("Something went wrong. Try again.");
}
});
client.on('ready', () => {
console.log(`π₯ Roast bot is online as ${client.user.tag}`);
});
client.login(process.env.DISCORD_TOKEN);
Step 4: Run it
export DISCORD_TOKEN=your-discord-bot-token
export ANTHROPIC_API_KEY=your-anthropic-key
node index.js
Go to your Discord server and try:
!roast
```javascript
for (var i = 0; i < arr.length; i++) {
setTimeout(function() { console.log(arr[i]); }, 1000);
}
## The two modes
The bot has two commands:
- **`!roast`** β brutal, funny, educational. For entertainment and learning.
- **`!review`** β professional, constructive. For when you actually want help.
Same AI, different personality. The prompt controls everything.
## Tuning the roast level
Adjust the prompt to control intensity:
**Mild roast:**
Be gently sarcastic, like a patient mentor whoβs slightly disappointed.
**Medium roast (default):**
Be brutally honest and funny, like a senior dev whoβs seen too much.
**Nuclear roast:**
Channel the energy of a developer who just found production code written by an intern with no code review. Hold nothing back.
## Rate limiting
Prevent spam by adding a cooldown:
```javascript
const cooldowns = new Map();
const COOLDOWN_MS = 30000; // 30 seconds
client.on('messageCreate', async (message) => {
if (!message.content.startsWith('!roast')) return;
const lastUsed = cooldowns.get(message.author.id) || 0;
if (Date.now() - lastUsed < COOLDOWN_MS) {
const remaining = Math.ceil((COOLDOWN_MS - (Date.now() - lastUsed)) / 1000);
message.reply(`Cooldown: ${remaining}s. Your code isn't going anywhere. π`);
return;
}
cooldowns.set(message.author.id, Date.now());
// ... rest of the handler
});
Deploying
Same as any Node.js bot β a VPS with pm2, a Docker container, or Railway/Fly.io:
# pm2
pm2 start index.js --name roast-bot
# Docker
docker build -t roast-bot .
docker run -d --env-file .env roast-bot
What you learned
- How to create a Discord bot with
discord.js - How to extract code blocks from Discord messages with regex
- How to use different AI prompts to control tone and personality
- How to handle Discordβs message length limits
- How to add rate limiting to prevent abuse
The bot is about 80 lines of core logic. Itβs the kind of thing that makes a dev Discord server 10x more fun β and people actually learn from the roasts because every joke comes with a real explanation.
Related resources
- JavaScript complete guide β the language behind the bot
- npm complete guide β managing dependencies
- Docker complete guide β deploying the bot