필사 모드: Complete Guide to Discord Bot Development: Slash Commands, Buttons, and Modals with Pycord
EnglishDiscord Bot Development Setup
Discord Developer Portal Configuration
1. Create a New Application at the [Discord Developer Portal](https://discord.com/developers/applications)
2. Copy the Token from the Bot tab (never expose this publicly!)
3. Generate a bot invite URL from the OAuth2 tab:
- Scopes: `bot`, `applications.commands`
- Permissions: Select the required permissions
Project Setup
Create a virtual environment
python -m venv venv
source venv/bin/activate
Install Pycord
pip install py-cord python-dotenv aiohttp
Project structure
my-discord-bot/
├── bot.py # Main bot file
├── cogs/
│ ├── __init__.py
│ ├── general.py # General commands
│ ├── moderation.py # Moderation commands
│ └── fun.py # Fun commands
├── utils/
│ └── helpers.py
├── .env
└── requirements.txt
.env File
DISCORD_TOKEN=your_bot_token_here
GUILD_IDS=123456789012345678
Basic Bot Structure
bot.py
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
Intents configuration
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
bot = discord.Bot(intents=intents)
@bot.event
async def on_ready():
print(f"✅ {bot.user} logged in!")
print(f"📊 Connected to {len(bot.guilds)} servers")
await bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name="Watching the server 👀"
)
)
Load Cogs
for filename in os.listdir("./cogs"):
if filename.endswith(".py") and not filename.startswith("_"):
bot.load_extension(f"cogs.{filename[:-3]}")
bot.run(os.getenv("DISCORD_TOKEN"))
Slash Commands
cogs/general.py
from discord.ext import commands
from discord import option
from datetime import datetime
class General(commands.Cog):
def __init__(self, bot):
self.bot = bot
@discord.slash_command(name="ping", description="Check the bot's response time")
async def ping(self, ctx: discord.ApplicationContext):
latency = round(self.bot.latency * 1000)
embed = discord.Embed(
title="🏓 Pong!",
description=f"Latency: **{latency}ms**",
color=discord.Color.green() if latency < 100 else discord.Color.red()
)
await ctx.respond(embed=embed)
@discord.slash_command(name="userinfo", description="Display user information")
@option("user", description="User to view info for", type=discord.Member, required=False)
async def userinfo(self, ctx: discord.ApplicationContext, user: discord.Member = None):
user = user or ctx.author
embed = discord.Embed(
title=f"👤 {user.display_name}",
color=user.color
)
embed.set_thumbnail(url=user.display_avatar.url)
embed.add_field(name="ID", value=user.id, inline=True)
embed.add_field(name="Joined", value=user.joined_at.strftime("%Y-%m-%d"), inline=True)
embed.add_field(name="Account Created", value=user.created_at.strftime("%Y-%m-%d"), inline=True)
embed.add_field(
name="Roles",
value=", ".join([r.mention for r in user.roles[1:]]) or "None",
inline=False
)
await ctx.respond(embed=embed)
@discord.slash_command(name="weather", description="Get weather information")
@option("city", description="City name", type=str, required=True)
async def weather(self, ctx: discord.ApplicationContext, city: str):
await ctx.defer() # Show response delay
async with aiohttp.ClientSession() as session:
url = f"https://wttr.in/{city}?format=j1"
async with session.get(url) as resp:
if resp.status != 200:
await ctx.followup.send("❌ City not found.")
return
data = await resp.json()
current = data["current_condition"][0]
embed = discord.Embed(
title=f"🌤 Weather in {city}",
color=discord.Color.blue()
)
embed.add_field(name="🌡 Temperature", value=f"{current['temp_C']}°C", inline=True)
embed.add_field(name="💧 Humidity", value=f"{current['humidity']}%", inline=True)
embed.add_field(name="💨 Wind", value=f"{current['windspeedKmph']} km/h", inline=True)
embed.add_field(name="Condition", value=current["weatherDesc"][0]["value"], inline=False)
await ctx.followup.send(embed=embed)
def setup(bot):
bot.add_cog(General(bot))
Button Interactions
cogs/fun.py
from discord.ext import commands
class RockPaperScissorsView(discord.ui.View):
def __init__(self):
super().__init__(timeout=30)
@discord.ui.button(label="✊ Rock", style=discord.ButtonStyle.primary, custom_id="rock")
async def rock(self, button: discord.ui.Button, interaction: discord.Interaction):
await self.play(interaction, "rock")
@discord.ui.button(label="✋ Paper", style=discord.ButtonStyle.success, custom_id="paper")
async def paper(self, button: discord.ui.Button, interaction: discord.Interaction):
await self.play(interaction, "paper")
@discord.ui.button(label="✌️ Scissors", style=discord.ButtonStyle.danger, custom_id="scissors")
async def scissors(self, button: discord.ui.Button, interaction: discord.Interaction):
await self.play(interaction, "scissors")
async def play(self, interaction: discord.Interaction, user_choice: str):
choices = {"rock": "✊", "paper": "✋", "scissors": "✌️"}
bot_choice = random.choice(list(choices.keys()))
if user_choice == bot_choice:
result = "🤝 Draw!"
color = discord.Color.yellow()
elif (user_choice == "rock" and bot_choice == "scissors") or \
(user_choice == "paper" and bot_choice == "rock") or \
(user_choice == "scissors" and bot_choice == "paper"):
result = "🎉 You Win!"
color = discord.Color.green()
else:
result = "😢 You Lose!"
color = discord.Color.red()
embed = discord.Embed(title=result, color=color)
embed.add_field(name="You", value=choices[user_choice], inline=True)
embed.add_field(name="Bot", value=choices[bot_choice], inline=True)
Disable buttons
for child in self.children:
child.disabled = True
await interaction.response.edit_message(embed=embed, view=self)
class Fun(commands.Cog):
def __init__(self, bot):
self.bot = bot
@discord.slash_command(name="rps", description="Rock Paper Scissors game!")
async def rps(self, ctx: discord.ApplicationContext):
embed = discord.Embed(
title="✊✋✌️ Rock Paper Scissors!",
description="Click a button to make your choice!",
color=discord.Color.blue()
)
await ctx.respond(embed=embed, view=RockPaperScissorsView())
def setup(bot):
bot.add_cog(Fun(bot))
Modal Forms
class FeedbackModal(discord.ui.Modal):
def __init__(self):
super().__init__(title="📋 Submit Feedback")
self.add_item(discord.ui.InputText(
label="Title",
placeholder="Enter the feedback title",
style=discord.InputTextStyle.short,
required=True,
max_length=100
))
self.add_item(discord.ui.InputText(
label="Content",
placeholder="Enter detailed content",
style=discord.InputTextStyle.long,
required=True,
max_length=2000
))
self.add_item(discord.ui.InputText(
label="Rating (1-5)",
placeholder="1",
style=discord.InputTextStyle.short,
required=False,
max_length=1
))
async def callback(self, interaction: discord.Interaction):
title = self.children[0].value
content = self.children[1].value
rating = self.children[2].value or "Not provided"
embed = discord.Embed(
title="📋 New Feedback",
color=discord.Color.blue()
)
embed.add_field(name="Title", value=title, inline=False)
embed.add_field(name="Content", value=content, inline=False)
embed.add_field(name="Rating", value=f"{'⭐' * int(rating)}" if rating.isdigit() else rating)
embed.set_footer(text=f"Author: {interaction.user.display_name}")
Send to feedback channel
feedback_channel = interaction.guild.get_channel(FEEDBACK_CHANNEL_ID)
if feedback_channel:
await feedback_channel.send(embed=embed)
await interaction.response.send_message(
"✅ Feedback submitted! Thank you.", ephemeral=True
)
Open modal via slash command
@discord.slash_command(name="feedback", description="Submit feedback")
async def feedback(ctx: discord.ApplicationContext):
await ctx.send_modal(FeedbackModal())
Select Menus
class RoleSelectView(discord.ui.View):
@discord.ui.select(
placeholder="Select roles (up to 3)",
min_values=1,
max_values=3,
options=[
discord.SelectOption(label="Developer", emoji="💻", value="developer"),
discord.SelectOption(label="Designer", emoji="🎨", value="designer"),
discord.SelectOption(label="Planner", emoji="📊", value="planner"),
discord.SelectOption(label="Marketer", emoji="📢", value="marketer"),
discord.SelectOption(label="Data Analyst", emoji="📈", value="analyst"),
]
)
async def select_callback(self, select: discord.ui.Select, interaction: discord.Interaction):
selected = ", ".join(select.values)
await interaction.response.send_message(
f"✅ Selected roles: {selected}", ephemeral=True
)
Error Handling
Add global error handler to bot.py
@bot.event
async def on_application_command_error(ctx: discord.ApplicationContext, error):
if isinstance(error, commands.MissingPermissions):
await ctx.respond("❌ Insufficient permissions.", ephemeral=True)
elif isinstance(error, commands.CommandOnCooldown):
await ctx.respond(
f"⏳ On cooldown. Please try again in {error.retry_after:.1f} seconds.",
ephemeral=True
)
elif isinstance(error, commands.MemberNotFound):
await ctx.respond("❌ User not found.", ephemeral=True)
else:
Logging
traceback.print_exception(type(error), error, error.__traceback__)
await ctx.respond("❌ An error occurred.", ephemeral=True)
Moderation Commands
cogs/moderation.py
class Moderation(commands.Cog):
def __init__(self, bot):
self.bot = bot
@discord.slash_command(name="clear", description="Delete messages")
@commands.has_permissions(manage_messages=True)
@option("amount", description="Number of messages to delete", type=int, min_value=1, max_value=100)
async def clear(self, ctx: discord.ApplicationContext, amount: int):
deleted = await ctx.channel.purge(limit=amount)
await ctx.respond(f"🗑️ {len(deleted)} messages deleted", ephemeral=True)
@discord.slash_command(name="slowmode", description="Set slow mode")
@commands.has_permissions(manage_channels=True)
@option("seconds", description="Seconds (0=disable)", type=int, min_value=0, max_value=21600)
async def slowmode(self, ctx: discord.ApplicationContext, seconds: int):
await ctx.channel.edit(slowmode_delay=seconds)
if seconds == 0:
await ctx.respond("✅ Slow mode has been disabled.")
else:
await ctx.respond(f"✅ Slow mode set to {seconds} seconds")
def setup(bot):
bot.add_cog(Moderation(bot))
Deployment
systemd Service
/etc/systemd/system/discord-bot.service
[Unit]
Description=Discord Bot
After=network.target
[Service]
Type=simple
User=bot
WorkingDirectory=/opt/discord-bot
ExecStart=/opt/discord-bot/venv/bin/python bot.py
Restart=always
RestartSec=10
EnvironmentFile=/opt/discord-bot/.env
[Install]
WantedBy=multi-user.target
sudo systemctl enable discord-bot
sudo systemctl start discord-bot
sudo journalctl -u discord-bot -f
**Q1. What are Discord Bot Intents?**
Intents are settings that specify the types of events the bot will receive. Privileged Intents (message_content, members) require separate activation in the Developer Portal.
**Q2. When do you use ctx.defer() in slash commands?**
When the response takes more than 3 seconds. After defer(), you send the actual response using ctx.followup.send().
**Q3. What does ephemeral=True mean?**
It makes the message visible only to the user who executed the command. Other users cannot see it.
**Q4. What are the advantages of Cogs?**
They allow you to separate commands into modules for better organization and enable dynamic loading/unloading. This is beneficial for code structure and maintainability.
**Q5. What does the View's timeout parameter control?**
The time in seconds until buttons/select menus are deactivated. Setting it to None means no timeout.
**Q6. What is the difference between a Modal and a regular message?**
A Modal displays an input form to the user for collecting structured data. Regular messages only exchange text.
Quiz
Q1: What is the main topic covered in "Complete Guide to Discord Bot Development: Slash
Commands, Buttons, and Modals with Pycord"?
A complete hands-on guide to developing a Discord Bot with Pycord. Covers slash commands, button
interactions, modal forms, embed messages, and Cog-based architecture at a production level.
Discord Developer Portal Configuration Create a New Application at the Discord Developer Portal
Copy the Token from the Bot tab (never expose this publicly!) Generate a bot invite URL from the
OAuth2 tab: Scopes: bot, applications.commands Permissions: Select the required permiss...
systemd Service Q1. What are Discord Bot Intents? Intents are settings that specify the types of
events the bot will receive. Privileged Intents (message_content, members) require separate
activation in the Developer Portal. Q2. When do you use ctx.defer() in slash commands?
현재 단락 (1/268)
1. Create a New Application at the [Discord Developer Portal](https://discord.com/developers/applica...