Skip to content

필사 모드: Discord Bot 개발 완벽 가이드: Pycord로 슬래시 커맨드, 버튼, 모달까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

Discord Bot 개발 준비

Discord 개발자 포털 설정

1. [Discord Developer Portal](https://discord.com/developers/applications)에서 New Application 생성

2. Bot 탭에서 Token 복사 (절대 공개하지 마세요!)

3. OAuth2 탭에서 봇 초대 URL 생성:

- Scopes: `bot`, `applications.commands`

- Permissions: 필요한 권한 선택

프로젝트 설정

가상 환경 생성

python -m venv venv

source venv/bin/activate

Pycord 설치

pip install py-cord python-dotenv aiohttp

프로젝트 구조

my-discord-bot/

├── bot.py # 메인 봇 파일

├── cogs/

│ ├── __init__.py

│ ├── general.py # 일반 명령어

│ ├── moderation.py # 관리 명령어

│ └── fun.py # 재미 명령어

├── utils/

│ └── helpers.py

├── .env

└── requirements.txt

.env 파일

DISCORD_TOKEN=your_bot_token_here

GUILD_IDS=123456789012345678

기본 봇 구조

bot.py

from discord.ext import commands

from dotenv import load_dotenv

load_dotenv()

Intents 설정

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} 로그인 완료!")

print(f"📊 {len(bot.guilds)}개 서버에 연결됨")

await bot.change_presence(

activity=discord.Activity(

type=discord.ActivityType.watching,

name="서버를 관찰 중 👀"

)

)

Cog 로드

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"))

슬래시 커맨드

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="봇의 응답 시간을 확인합니다")

async def ping(self, ctx: discord.ApplicationContext):

latency = round(self.bot.latency * 1000)

embed = discord.Embed(

title="🏓 Pong!",

description=f"지연 시간: **{latency}ms**",

color=discord.Color.green() if latency < 100 else discord.Color.red()

)

await ctx.respond(embed=embed)

@discord.slash_command(name="userinfo", description="사용자 정보를 표시합니다")

@option("user", description="정보를 볼 사용자", 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="가입일", value=user.joined_at.strftime("%Y-%m-%d"), inline=True)

embed.add_field(name="계정 생성일", value=user.created_at.strftime("%Y-%m-%d"), inline=True)

embed.add_field(

name="역할",

value=", ".join([r.mention for r in user.roles[1:]]) or "없음",

inline=False

)

await ctx.respond(embed=embed)

@discord.slash_command(name="weather", description="날씨 정보를 가져옵니다")

@option("city", description="도시 이름", type=str, required=True)

async def weather(self, ctx: discord.ApplicationContext, city: str):

await ctx.defer() # 응답 지연 표시

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("❌ 도시를 찾을 수 없습니다.")

return

data = await resp.json()

current = data["current_condition"][0]

embed = discord.Embed(

title=f"🌤 {city} 날씨",

color=discord.Color.blue()

)

embed.add_field(name="🌡 기온", value=f"{current['temp_C']}°C", inline=True)

embed.add_field(name="💧 습도", value=f"{current['humidity']}%", inline=True)

embed.add_field(name="💨 바람", value=f"{current['windspeedKmph']} km/h", inline=True)

embed.add_field(name="상태", value=current["weatherDesc"][0]["value"], inline=False)

await ctx.followup.send(embed=embed)

def setup(bot):

bot.add_cog(General(bot))

버튼 인터랙션

cogs/fun.py

from discord.ext import commands

class RockPaperScissorsView(discord.ui.View):

def __init__(self):

super().__init__(timeout=30)

@discord.ui.button(label="✊ 바위", 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="✋ 보", 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="✌️ 가위", 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 = "🤝 무승부!"

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 = "🎉 승리!"

color = discord.Color.green()

else:

result = "😢 패배!"

color = discord.Color.red()

embed = discord.Embed(title=result, color=color)

embed.add_field(name="당신", value=choices[user_choice], inline=True)

embed.add_field(name="봇", value=choices[bot_choice], inline=True)

버튼 비활성화

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="가위바위보 게임!")

async def rps(self, ctx: discord.ApplicationContext):

embed = discord.Embed(

title="✊✋✌️ 가위바위보!",

description="버튼을 클릭해서 선택하세요!",

color=discord.Color.blue()

)

await ctx.respond(embed=embed, view=RockPaperScissorsView())

def setup(bot):

bot.add_cog(Fun(bot))

모달 (Modal) 폼

class FeedbackModal(discord.ui.Modal):

def __init__(self):

super().__init__(title="📋 피드백 제출")

self.add_item(discord.ui.InputText(

label="제목",

placeholder="피드백 제목을 입력하세요",

style=discord.InputTextStyle.short,

required=True,

max_length=100

))

self.add_item(discord.ui.InputText(

label="내용",

placeholder="상세 내용을 입력하세요",

style=discord.InputTextStyle.long,

required=True,

max_length=2000

))

self.add_item(discord.ui.InputText(

label="점수 (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 "미입력"

embed = discord.Embed(

title="📋 새 피드백",

color=discord.Color.blue()

)

embed.add_field(name="제목", value=title, inline=False)

embed.add_field(name="내용", value=content, inline=False)

embed.add_field(name="점수", value=f"{'⭐' * int(rating)}" if rating.isdigit() else rating)

embed.set_footer(text=f"작성자: {interaction.user.display_name}")

피드백 채널에 전송

feedback_channel = interaction.guild.get_channel(FEEDBACK_CHANNEL_ID)

if feedback_channel:

await feedback_channel.send(embed=embed)

await interaction.response.send_message(

"✅ 피드백이 제출되었습니다! 감사합니다.", ephemeral=True

)

슬래시 커맨드로 모달 열기

@discord.slash_command(name="feedback", description="피드백을 제출합니다")

async def feedback(ctx: discord.ApplicationContext):

await ctx.send_modal(FeedbackModal())

Select 메뉴

class RoleSelectView(discord.ui.View):

@discord.ui.select(

placeholder="역할을 선택하세요 (최대 3개)",

min_values=1,

max_values=3,

options=[

discord.SelectOption(label="개발자", emoji="💻", value="developer"),

discord.SelectOption(label="디자이너", emoji="🎨", value="designer"),

discord.SelectOption(label="기획자", emoji="📊", value="planner"),

discord.SelectOption(label="마케터", emoji="📢", value="marketer"),

discord.SelectOption(label="데이터 분석가", 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}", ephemeral=True

)

에러 처리

bot.py에 글로벌 에러 핸들러 추가

@bot.event

async def on_application_command_error(ctx: discord.ApplicationContext, error):

if isinstance(error, commands.MissingPermissions):

await ctx.respond("❌ 권한이 부족합니다.", ephemeral=True)

elif isinstance(error, commands.CommandOnCooldown):

await ctx.respond(

f"⏳ 쿨다운 중입니다. {error.retry_after:.1f}초 후 다시 시도하세요.",

ephemeral=True

)

elif isinstance(error, commands.MemberNotFound):

await ctx.respond("❌ 사용자를 찾을 수 없습니다.", ephemeral=True)

else:

로깅

traceback.print_exception(type(error), error, error.__traceback__)

await ctx.respond("❌ 오류가 발생했습니다.", ephemeral=True)

관리 명령어

cogs/moderation.py

class Moderation(commands.Cog):

def __init__(self, bot):

self.bot = bot

@discord.slash_command(name="clear", description="메시지를 삭제합니다")

@commands.has_permissions(manage_messages=True)

@option("amount", description="삭제할 메시지 수", 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)}개 메시지 삭제됨", ephemeral=True)

@discord.slash_command(name="slowmode", description="슬로우모드를 설정합니다")

@commands.has_permissions(manage_channels=True)

@option("seconds", description="초 단위 (0=해제)", 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("✅ 슬로우모드가 해제되었습니다.")

else:

await ctx.respond(f"✅ 슬로우모드: {seconds}초로 설정됨")

def setup(bot):

bot.add_cog(Moderation(bot))

배포

systemd 서비스

/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. Discord Bot의 Intents란?**

봇이 수신할 이벤트 유형을 지정하는 설정입니다. Privileged Intents(message_content, members)는 Developer Portal에서 별도 활성화가 필요합니다.

**Q2. 슬래시 커맨드에서 ctx.defer()는 언제 사용하나요?**

응답이 3초 이상 걸릴 때 사용합니다. defer() 후 ctx.followup.send()로 실제 응답을 보냅니다.

**Q3. ephemeral=True의 의미는?**

해당 메시지가 명령어를 실행한 사용자에게만 보이게 합니다. 다른 사용자는 볼 수 없습니다.

**Q4. Cog의 장점은?**

명령어를 모듈별로 분리하여 관리하고, 동적으로 로드/언로드할 수 있습니다. 코드 구조화와 유지보수에 유리합니다.

**Q5. View의 timeout 파라미터는 무엇을 제어하나요?**

버튼/셀렉트 메뉴가 비활성화되기까지의 시간(초)입니다. None으로 설정하면 타임아웃 없음.

**Q6. Modal과 일반 메시지의 차이점은?**

Modal은 사용자에게 입력 폼을 표시하여 구조화된 데이터를 받을 수 있습니다. 일반 메시지는 텍스트만 주고받습니다.

현재 단락 (1/258)

1. [Discord Developer Portal](https://discord.com/developers/applications)에서 New Application 생성

작성 글자: 0원문 글자: 9,427작성 단락: 0/258