Command Bot Example
A bot with a comprehensive command system, permissions, and moderation features.
Project Structure
command_bot/
├── Gemfile
├── bot.rb
├── config.rb
├── commands/
│ ├── moderation.rb
│ ├── utility.rb
│ └── fun.rb
└── .env
Files
Gemfile
source 'https://rubygems.org'
gem 'discord_rda'
gem 'dotenv'
gem 'sqlite3'
config.rb
require 'dotenv'
Dotenv.load
module BotConfig
TOKEN = ENV['DISCORD_TOKEN']
APP_ID = ENV['DISCORD_APP_ID']
INTENTS = [
:guilds,
:guild_messages,
:message_content,
:guild_members,
:guild_moderation
]
LOG_LEVEL = ENV['RACK_ENV'] == 'development' ? :debug : :info
MODERATOR_ROLES = ['Admin', 'Moderator']
LOG_CHANNEL = ENV['LOG_CHANNEL_ID']
end
commands/moderation.rb
module Commands
module Moderation
def self.register(bot)
# Kick command
bot.slash('kick', 'Kick a user from the server') do |cmd|
cmd.user('user', 'User to kick', required: true)
cmd.string('reason', 'Reason for kick')
cmd.default_permissions(:kick_members)
cmd.handler do |interaction|
user = interaction.option('user')
reason = interaction.option('reason') || 'No reason provided'
# Check if user is kickable
target_member = bot.guild_member(interaction.guild_id, user.id)
if target_member.permissions.administrator?
interaction.respond(
content: '❌ Cannot kick an administrator!',
ephemeral: true
)
return
end
# Kick the user
bot.remove_guild_member(
interaction.guild_id,
user.id,
reason: reason
)
# Log action
log_moderation_action(
bot,
interaction.guild_id,
'Kick',
interaction.user,
user,
reason
)
interaction.respond(
content: "👢 Kicked #{user.username}\n**Reason:** #{reason}",
ephemeral: true
)
end
end
# Ban command
bot.slash('ban', 'Ban a user from the server') do |cmd|
cmd.user('user', 'User to ban', required: true)
cmd.string('reason', 'Reason for ban')
cmd.integer('days', 'Days of messages to delete (0-7)',
min_value: 0,
max_value: 7
)
cmd.default_permissions(:ban_members)
cmd.handler do |interaction|
user = interaction.option('user')
reason = interaction.option('reason') || 'No reason provided'
days = interaction.option('days') || 0
# Ban the user
bot.create_guild_ban(
interaction.guild_id,
user.id,
delete_message_days: days,
reason: reason
)
# Log action
log_moderation_action(
bot,
interaction.guild_id,
'Ban',
interaction.user,
user,
reason
)
interaction.respond(
content: "🔨 Banned #{user.username}\n**Reason:** #{reason}\n**Delete Messages:** #{days} days",
ephemeral: true
)
end
end
# Mute command (timeout)
bot.slash('mute', 'Timeout a user') do |cmd|
cmd.user('user', 'User to mute', required: true)
cmd.integer('minutes', 'Duration in minutes', required: true)
cmd.string('reason', 'Reason')
cmd.default_permissions(:moderate_members)
cmd.handler do |interaction|
user = interaction.option('user')
minutes = interaction.option('minutes')
reason = interaction.option('reason') || 'No reason'
# Calculate timeout until
timeout_until = Time.now + (minutes * 60)
# Apply timeout
bot.modify_guild_member(
interaction.guild_id,
user.id,
communication_disabled_until: timeout_until.iso8601
)
log_moderation_action(
bot,
interaction.guild_id,
'Mute',
interaction.user,
user,
"#{minutes}min - #{reason}"
)
interaction.respond(
content: "🔇 Muted #{user.username} for #{minutes} minutes",
ephemeral: true
)
end
end
# Purge command
bot.slash('purge', 'Delete multiple messages') do |cmd|
cmd.integer('amount', 'Number of messages to delete',
required: true,
min_value: 1,
max_value: 100
)
cmd.user('user', 'Only delete messages from this user')
cmd.default_permissions(:manage_messages)
cmd.handler do |interaction|
amount = interaction.option('amount')
target_user = interaction.option('user')
# Defer since this might take a moment
interaction.defer(ephemeral: true)
# Get messages
messages = bot.channel_messages(
interaction.channel_id,
limit: amount
)
# Filter by user if specified
if target_user
messages = messages.select { |m| m.author.id == target_user.id }
end
# Must be 2-100 messages for bulk delete
if messages.length < 2
interaction.edit_original(
content: 'Not enough messages to delete (need at least 2)'
)
return
end
# Delete messages
bot.bulk_delete_messages(
interaction.channel_id,
messages.map(&:id)
)
log_moderation_action(
bot,
interaction.guild_id,
'Purge',
interaction.user,
nil,
"Deleted #{messages.length} messages"
)
interaction.edit_original(
content: "🗑️ Deleted #{messages.length} messages"
)
end
end
# Warn command
bot.slash('warn', 'Warn a user') do |cmd|
cmd.user('user', 'User to warn', required: true)
cmd.string('reason', 'Reason for warning', required: true)
cmd.default_permissions(:kick_members)
cmd.handler do |interaction|
user = interaction.option('user')
reason = interaction.option('reason')
# Store warning (in production, use database)
warn_id = store_warning(
guild_id: interaction.guild_id,
user_id: user.id,
moderator_id: interaction.user.id,
reason: reason
)
# Count total warnings
total_warns = count_warnings(interaction.guild_id, user.id)
# DM the user
begin
bot.send_message(
user.id,
"You received a warning in **#{interaction.guild.name}**\n" \
"**Reason:** #{reason}\n" \
"**Total warnings:** #{total_warns}"
)
rescue
# Can't DM user
end
log_moderation_action(
bot,
interaction.guild_id,
'Warning',
interaction.user,
user,
reason
)
interaction.respond(
content: "⚠️ Warned #{user.username}\n**Reason:** #{reason}\n**Total warnings:** #{total_warns}",
ephemeral: true
)
end
end
# Warnings command
bot.slash('warnings', 'View user warnings') do |cmd|
cmd.user('user', 'User to check', required: true)
cmd.default_permissions(:kick_members)
cmd.handler do |interaction|
user = interaction.option('user')
warnings = get_warnings(interaction.guild_id, user.id)
if warnings.empty?
interaction.respond(
content: "#{user.username} has no warnings",
ephemeral: true
)
return
end
list = warnings.map do |w|
"**##{w[:id]}** - #{w[:reason]}\n" \
"By <@#{w[:moderator_id]}> - <t:#{w[:timestamp].to_i}:R>"
end.join("\n\n")
interaction.respond(
content: "⚠️ Warnings for #{user.username} (#{warnings.length} total):\n\n#{list}",
ephemeral: true
)
end
end
end
private
def self.log_moderation_action(bot, guild_id, action, moderator, target, reason)
return unless BotConfig::LOG_CHANNEL
target_text = target ? "#{target.username} (#{target.id})" : 'N/A'
embed = {
title: "#{action} Action",
color: case action
when 'Ban' then 0xff0000
when 'Kick' then 0xffa500
when 'Mute' then 0xffff00
else 0x808080
end,
fields: [
{ name: 'Moderator', value: "#{moderator.username} (#{moderator.id})", inline: true },
{ name: 'Target', value: target_text, inline: true },
{ name: 'Reason', value: reason, inline: false }
],
timestamp: Time.now.iso8601
}
bot.send_message(
BotConfig::LOG_CHANNEL,
'',
embeds: [embed]
)
end
# In production, use a database
@@warnings = []
def self.store_warning(data)
@@warnings << data.merge(id: @@warnings.length + 1, timestamp: Time.now)
@@warnings.length
end
def self.count_warnings(guild_id, user_id)
@@warnings.count { |w| w[:guild_id] == guild_id && w[:user_id] == user_id }
end
def self.get_warnings(guild_id, user_id)
@@warnings.select { |w| w[:guild_id] == guild_id && w[:user_id] == user_id }
end
end
end
commands/utility.rb
module Commands
module Utility
def self.register(bot)
# User info
bot.slash('userinfo', 'Get user information') do |cmd|
cmd.user('user', 'User to look up')
cmd.handler do |interaction|
user = interaction.option('user') || interaction.user
member = user.id == interaction.user.id ?
interaction.member :
bot.guild_member(interaction.guild_id, user.id)
embed = {
title: user.username,
thumbnail: { url: user.avatar_url },
color: 0x7289da,
fields: [
{ name: 'ID', value: user.id, inline: true },
{ name: 'Created', value: "<t:#{user.created_at.to_i}:R>", inline: true }
]
}
if member
embed[:fields].concat([
{ name: 'Nickname', value: member.nick || 'None', inline: true },
{ name: 'Joined', value: "<t:#{member.joined_at.to_i}:R>", inline: true },
{ name: 'Roles', value: member.roles.length.to_s, inline: true },
{ name: 'Boosting Since', value: member.premium_since ? "<t:#{member.premium_since.to_i}:R>" : 'Not boosting', inline: true }
])
end
interaction.respond(embeds: [embed], ephemeral: true)
end
end
# Avatar command
bot.slash('avatar', 'Get user avatar') do |cmd|
cmd.user('user', 'User to get avatar of')
cmd.handler do |interaction|
user = interaction.option('user') || interaction.user
embed = {
title: "#{user.username}'s Avatar",
image: { url: user.avatar_url(size: 4096) },
color: 0x7289da
}
interaction.respond(embeds: [embed])
end
end
# Poll command
bot.slash('poll', 'Create a poll') do |cmd|
cmd.string('question', 'Poll question', required: true)
cmd.string('option1', 'First option', required: true)
cmd.string('option2', 'Second option', required: true)
cmd.string('option3', 'Third option')
cmd.string('option4', 'Fourth option')
cmd.handler do |interaction|
question = interaction.option('question')
options = [
interaction.option('option1'),
interaction.option('option2'),
interaction.option('option3'),
interaction.option('option4')
].compact
emojis = ['1️⃣', '2️⃣', '3️⃣', '4️⃣']
options_text = options.map.with_index do |opt, i|
"#{emojis[i]} #{opt}"
end.join("\n")
message = interaction.respond(
content: "📊 **#{question}**\n\n#{options_text}"
)
# Add reactions
options.length.times do |i|
bot.add_reaction(interaction.channel_id, message.id, emojis[i])
end
end
end
# Remind command
bot.slash('remind', 'Set a reminder') do |cmd|
cmd.string('message', 'What to remind you about', required: true)
cmd.integer('minutes', 'Minutes from now', required: true, min_value: 1)
cmd.handler do |interaction|
message = interaction.option('message')
minutes = interaction.option('minutes')
remind_at = Time.now + (minutes * 60)
# Schedule reminder
Thread.new do
sleep(minutes * 60)
begin
bot.send_message(
interaction.channel_id,
"⏰ #{interaction.user.mention} Reminder: #{message}"
)
rescue
# Channel no longer accessible
end
end
interaction.respond(
content: "⏰ I'll remind you in #{minutes} minutes (<t:#{remind_at.to_i}:R>)",
ephemeral: true
)
end
end
end
end
end
commands/fun.rb
module Commands
module Fun
def self.register(bot)
# 8ball
bot.slash('8ball', 'Ask the magic 8-ball') do |cmd|
cmd.string('question', 'Your question', required: true)
cmd.handler do |interaction|
responses = [
'It is certain.',
'It is decidedly so.',
'Without a doubt.',
'Yes definitely.',
'You may rely on it.',
'As I see it, yes.',
'Most likely.',
'Outlook good.',
'Yes.',
'Signs point to yes.',
'Reply hazy, try again.',
'Ask again later.',
'Better not tell you now.',
'Cannot predict now.',
'Concentrate and ask again.',
"Don't count on it.",
'My reply is no.',
'My sources say no.',
'Outlook not so good.',
'Very doubtful.'
]
question = interaction.option('question')
answer = responses.sample
embed = {
title: '🎱 Magic 8-Ball',
fields: [
{ name: 'Question', value: question },
{ name: 'Answer', value: answer }
],
color: 0x4b0082
}
interaction.respond(embeds: [embed])
end
end
# Roll dice
bot.slash('roll', 'Roll dice') do |cmd|
cmd.integer('sides', 'Number of sides', min_value: 2, max_value: 100)
cmd.integer('count', 'Number of dice', min_value: 1, max_value: 10)
cmd.handler do |interaction|
sides = interaction.option('sides') || 6
count = interaction.option('count') || 1
rolls = count.times.map { rand(1..sides) }
total = rolls.sum
if count == 1
result = "🎲 Rolled **#{rolls.first}** (d#{sides})"
else
result = "🎲 Rolled: #{rolls.join(', ')}\n**Total:** #{total}"
end
interaction.respond(content: result)
end
end
# Coin flip
bot.slash('coinflip', 'Flip a coin') do |cmd|
cmd.handler do |interaction|
result = ['Heads', 'Tails'].sample
emoji = result == 'Heads' ? '👤' : '🦅'
interaction.respond(content: "#{emoji} **#{result}**")
end
end
# Choose
bot.slash('choose', 'Choose between options') do |cmd|
cmd.string('options', 'Comma-separated options', required: true)
cmd.handler do |interaction|
options = interaction.option('options').split(',').map(&:strip)
if options.length < 2
interaction.respond(
content: '❌ Please provide at least 2 options separated by commas',
ephemeral: true
)
return
end
choice = options.sample
interaction.respond(
content: "🤔 I choose: **#{choice}**"
)
end
end
end
end
end
bot.rb
require 'discord_rda'
require_relative 'config'
require_relative 'commands/moderation'
require_relative 'commands/utility'
require_relative 'commands/fun'
# Create bot
bot = DiscordRDA::Bot.new(
token: BotConfig::TOKEN,
application_id: BotConfig::APP_ID,
intents: BotConfig::INTENTS,
log_level: BotConfig::LOG_LEVEL
)
# Register commands
Commands::Moderation.register(bot)
Commands::Utility.register(bot)
Commands::Fun.register(bot)
# Ready event
bot.on(:ready) do |event|
puts "✅ Command Bot ready!"
puts "Logged in as: #{event.user.username}"
puts "Guilds: #{bot.status[:guild_count]}"
end
# Error handling
bot.on(:dispatch) do |event|
begin
# Let it process
rescue => e
puts "Error handling event: #{e.message}"
puts e.backtrace.first(5)
end
end
puts "Starting command bot..."
bot.run
Running
bundle install
export DISCORD_TOKEN="your_token"
export DISCORD_APP_ID="your_app_id"
ruby bot.rb
Features
- ✅ Moderation: kick, ban, mute, warn, purge
- ✅ Utility: userinfo, avatar, poll, remind
- ✅ Fun: 8ball, roll, coinflip, choose
- ✅ Permission checks
- ✅ Logging
- ✅ DM notifications for warnings