Your first bot
This walks you from nothing to a running bot that replies to a slash command, explained line by line. It assumes you have done Installing Node and your tools.
This walks you from nothing to a running bot that replies to a slash command, explained line by line. It assumes you have done Installing Node and your tools.
1. Create a Discord application and bot
- Go to the Discord Developer Portal.
- Click New Application, give it a name.
- Open the Bot tab. Click Reset Token and copy the token. Treat it like a password: anyone with it controls your bot. Never commit it or share it.
- Still on the Bot tab, scroll to Privileged Gateway Intents. For this tutorial you do not need any of them on.
- Open the OAuth2 -> URL Generator. Tick
botandapplications.commands. Copy the generated URL, open it, and invite the bot to a server you own.
2. Store your token safely
In your project folder create a file named .env:
DISCORD_TOKEN=paste-your-token-here
Create another file named .gitignore so you never accidentally upload secrets or junk:
node_modules
.env
dist
Install a tiny helper to read the .env file:
npm install dotenv3. Write the bot
Create src/index.ts:
import 'dotenv/config';
import { CommandClient, GatewayIntentBits } from 'athena';
import PingCommand from './commands/ping';
const client = new CommandClient({
token: `Bot ${process.env.DISCORD_TOKEN}`,
options: { intents: [GatewayIntentBits.Guilds] }
});
client.registerCommand(new PingCommand());
client.on('ready', async () => {
console.log(`Logged in as ${client.user.username}`);
await client.deployCommands();
});
client.on('error', (err) => console.error(err));
void client.connect();Line by line:
import 'dotenv/config'reads.envsoprocess.env.DISCORD_TOKENworks.CommandClientis Athena's helper for slash commands. The token must start withBot.intentslists which events you want.Guildsis enough for slash commands. See Intents.registerCommandadds a command (we write it next).- On
ready, we tell Discord about our commands withdeployCommands(). - Always listen for
errorso problems are visible. connect()logs the bot in.
4. Write the command
Create src/commands/ping.ts:
import { Command, CommandBuilder, SlashCommand } from 'athena';
import type { CommandInteraction } from 'athena';
@SlashCommand(new CommandBuilder('ping', 'Replies with pong'))
export default class PingCommand extends Command {
async handleCommand(context: unknown, interaction: CommandInteraction) {
await interaction.createMessage({ content: 'Pong!' });
}
}@SlashCommand(...)attaches the command's name and description.CommandBuilder('ping', 'Replies with pong')defines a/pingcommand.handleCommandruns when someone uses/ping. It replies with "Pong!".
If the decorator line shows an error in your editor, enable decorators by adding "experimentalDecorators": true to the compilerOptions in tsconfig.json.
5. Run it
For instant command updates while developing, point Athena at one test server. Find your server ID (enable Developer Mode in Discord settings, right click the server, Copy Server ID), then run:
DEV_GUILD=your-server-id npx tsx src/index.tsYou should see "Logged in as ...". In Discord, type / in your test server and you will see ping. Run it; the bot replies "Pong!".
If /ping does not appear, give it a moment and retype /. Guild commands (from DEV_GUILD) appear within seconds; global commands can take up to an hour, which is why we use a dev guild while building.
What just happened
CommandClientconnected to Discord's gateway (a live websocket) and logged in.deployCommands()registered/pingwith Discord.- When you ran
/ping, Discord sent your bot an interaction, Athena turned it into aCommandInteraction, found yourPingCommand, and calledhandleCommand.
Next steps
- Add options to your command: Slash commands.
- Add buttons and menus: Components.
- Understand the structure of a real project: Project template.
- See everything a command can do: Commands framework.