Python Guide
This guide walks you through deploying a Discord bot in Python on KataBump. We'll use the discord.py library, which is the reference for Python.
Prerequisites
- Python 3.8+ installed on your machine
- A Discord bot created on the Developer Portal
- Your bot's token
Create Your Project
Recommended Structure
/my-discord-bot/
├── requirements.txt
├── main.py
├── config.py
└── cogs/
└── moderation.py1. Create a Virtual Environment
mkdir my-discord-bot
cd my-discord-bot
python -m venv venv
# Activate the environment
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate2. Create requirements.txt
discord.py==2.3.2Alternative Libraries
Several forks exist (nextcord, pycord). The syntax is similar — adapt according to your choice.
3. Install Dependencies
pip install -r requirements.txt4. Create the Bot
Create a main.py file:
import discord
import os
from discord.ext import commands
# Configure intents
intents = discord.Intents.default()
intents.message_content = True # Required to read message content
# Create the bot
bot = commands.Bot(
command_prefix='!',
intents=intents,
help_command=None # Disable default help command
)
# "on_ready" event
@bot.event
async def on_ready():
print(f'Connected as {bot.user}')
print(f'ID: {bot.user.id}')
print('------')
# Change bot status
await bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name='!help commands'
)
)
# !ping command
@bot.command()
async def ping(ctx):
"""Responds with Pong!"""
latency = round(bot.latency * 1000)
await ctx.send(f'Pong! `{latency}ms`')
# !hello command
@bot.command()
async def hello(ctx):
"""Greets the user"""
await ctx.send(f'Hello {ctx.author.mention}!')
# Custom !help command
@bot.command()
async def help(ctx):
"""Displays help"""
embed = discord.Embed(
title='Available Commands',
color=discord.Color.blue()
)
embed.add_field(name='!ping', value='Responds with Pong!', inline=False)
embed.add_field(name='!hello', value='Greets the user', inline=False)
embed.add_field(name='!help', value='Displays this help', inline=False)
await ctx.send(embed=embed)
# Error handling
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
return # Ignore unknown commands
if isinstance(error, commands.MissingPermissions):
await ctx.send('You do not have permission to use this command.')
return
# Log other errors
print(f'Error: {error}')
# Get token from environment variables
TOKEN = os.getenv('DISCORD_TOKEN')
if not TOKEN:
print('Error: DISCORD_TOKEN variable is not defined')
exit(1)
# Start the bot
bot.run(TOKEN)5. Set Up Environment Variables
Create a .env file at the root of your project:
DISCORD_TOKEN=your_token_hereInstall python-dotenv and add it to requirements.txt:
pip install python-dotenvdiscord.py==2.3.2
python-dotenv==1.0.0Add the following at the top of main.py:
from dotenv import load_dotenv
load_dotenv().env on KataBump
The .env file is used both locally and on KataBump. Upload it to your server along with your other files. See Set Environment Variables for more details.
6. Test Locally (optional)
python main.py::: success Works Locally? If your bot works locally, it will work on KataBump! :::
Deploy to KataBump
1. Create a Python Server
- Log in to dashboard.katabump.com
- Click "Create a server"
- Choose Python
- Name your server
- Click "Create"
- On control.katabump.com, go to the "Startup" tab to select your Python version (recommended: 3.10 or 3.11)
2. Upload Files
Via Web
- Create a ZIP file containing your project files
- Go to the "Files" tab on control.katabump.com
- Drag and drop your ZIP file
- Unzip the file on the panel
- Make sure
requirements.txtandmain.pyare at the root
Via SFTP
- Retrieve your SFTP credentials in the "Settings" section
- Connect with FileZilla
- Upload files to
/home/container
3. Configure Startup
- Go to the "Startup" tab on control.katabump.com
- Set the "PY FILE" to your entry point (e.g.
main.py) - Save
Environment Variables
Your .env file (containing your DISCORD_TOKEN) should already be included in your uploaded files. If not, upload it via the file manager or SFTP.
4. Start the Server
- Go to the "Console" tab
- Click "Start"
- Wait for dependency installation (automatic pip install)
- You should see:
Connected as MyBot#1234 ID: 123456789 ------
::: success Bot Online! Your bot is now connected 24/7! :::
Slash Commands
Slash commands are the new Discord standard.
Example with discord.py
import discord
from discord import app_commands
import os
class MyBot(discord.Client):
def __init__(self):
super().__init__(intents=discord.Intents.default())
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
# Sync commands (do this once)
await self.tree.sync()
print('Slash commands synced')
async def on_ready(self):
print(f'Connected as {self.user}')
bot = MyBot()
# /ping slash command
@bot.tree.command(name="ping", description="Responds with Pong!")
async def ping(interaction: discord.Interaction):
latency = round(bot.latency * 1000)
await interaction.response.send_message(f'Pong! `{latency}ms`')
# /hello slash command
@bot.tree.command(name="hello", description="Greets the user")
async def hello(interaction: discord.Interaction):
await interaction.response.send_message(f'Hello {interaction.user.mention}!')
# Command with parameter
@bot.tree.command(name="echo", description="Repeats a message")
@app_commands.describe(message="The message to repeat")
async def echo(interaction: discord.Interaction, message: str):
await interaction.response.send_message(f'{message}')
# Admin-only command
@bot.tree.command(name="admin", description="Admin command")
@app_commands.checks.has_permissions(administrator=True)
async def admin(interaction: discord.Interaction):
await interaction.response.send_message('Admin command executed!', ephemeral=True)
@admin.error
async def admin_error(interaction: discord.Interaction, error):
if isinstance(error, app_commands.MissingPermissions):
await interaction.response.send_message('You must be an administrator!', ephemeral=True)
bot.run(os.getenv('DISCORD_TOKEN'))Synchronization
Slash commands must be synced with Discord. For large bots, do it manually or via a specific command rather than at startup.
Structure with Cogs (recommended)
For large projects, use Cogs to organize your code:
/my-bot/
├── requirements.txt
├── main.py
└── cogs/
├── __init__.py
├── moderation.py
└── fun.pymain.py
import discord
from discord.ext import commands
import os
import asyncio
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='!', intents=intents)
async def load_extensions():
for filename in os.listdir('./cogs'):
if filename.endswith('.py'):
await bot.load_extension(f'cogs.{filename[:-3]}')
print(f'Extension {filename} loaded')
@bot.event
async def on_ready():
print(f'Connected as {bot.user}')
async def main():
async with bot:
await load_extensions()
await bot.start(os.getenv('DISCORD_TOKEN'))
asyncio.run(main())cogs/moderation.py
import discord
from discord.ext import commands
from discord import app_commands
class Moderation(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command()
@commands.has_permissions(kick_members=True)
async def kick(self, ctx, member: discord.Member, *, reason=None):
"""Kicks a member"""
await member.kick(reason=reason)
await ctx.send(f'{member.mention} has been kicked.')
@app_commands.command(name="ban", description="Bans a member")
@app_commands.describe(member="The member to ban", reason="The reason for the ban")
@app_commands.checks.has_permissions(ban_members=True)
async def ban_slash(self, interaction: discord.Interaction, member: discord.Member, reason: str = None):
await member.ban(reason=reason)
await interaction.response.send_message(f'{member.mention} has been banned.')
async def setup(bot):
await bot.add_cog(Moderation(bot))Best Practices
Error Handling
import logging
# Logging configuration
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
@bot.event
async def on_error(event, *args, **kwargs):
logging.error(f'Error in {event}: ', exc_info=True)
@bot.event
async def on_command_error(ctx, error):
if isinstance(error, commands.CommandNotFound):
return
logging.error(f'Command error: {error}')Database Connection
import sqlite3
from contextlib import closing
# SQLite (local file)
DB_PATH = '/home/container/database.db'
def get_db():
return sqlite3.connect(DB_PATH)
# Usage example
@bot.command()
async def addquote(ctx, *, text):
with closing(get_db()) as db:
db.execute('INSERT INTO quotes (text, author) VALUES (?, ?)',
(text, ctx.author.name))
db.commit()
await ctx.send('Quote added!')Database
For SQLite, data is stored in the server folder. Paid plans include MariaDB databases — see Plans for details.
Memory Optimization
import gc
@bot.event
async def on_ready():
# Force garbage collection on startup
gc.collect()
print(f'Connected as {bot.user}')
# Periodic cleanup (optional)
@tasks.loop(hours=1)
async def cleanup():
gc.collect()
print('Memory cleanup done')Free Plan Memory
The free plan provides 308 MB RAM. Optimize your code to stay within limits.