디스코드 봇 만들기#19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제)
목차15개 항목
안녕하세요! Mojangcam입니다 😎
지난 #18편에서는 discord.Poll 로 네이티브 투표 기능을 만들었죠. 그런데 여기까지 오면서 눈치채신 분 계실 거예요. bot.py 파일이 슬슬... 아니 꽤 많이 길어졌잖아요? 😅 명령어 하나 고치려고 스크롤을 300줄씩 내리고 계신다면, 오늘 글이 딱입니다.
이번 편에서 배울 것
- 확장(Extension)과 Cog가 뭐가 다른지
- 봇 코드를 기능별 파일로 쪼개는 법
cogs/폴더의 파일을 자동으로 전부 불러오기- 봇을 껐다 켜지 않고 코드만 다시 불러오는 핫리로드
- 지난 편 투표 명령어를
cogs/poll.py로 이사시키기
그리고 오늘부터가 진짜 중요해요. 여기서 잡는 폴더 구조가 시리즈 최종 목표인 커뮤니티 통합 관리 봇의 뼈대가 되거든요. #20부터 만드는 기능들은 전부 이 구조 위에 파일 하나씩 얹는 방식으로 진행됩니다 🏗️

🧠 왜 파일을 쪼개야 할까요?
한 파일에 다 넣어도 봇은 잘 돌아가요. 문제는 사람입니다.
- 명령어가 20개쯤 되면 원하는 함수 찾는 데만 1분이 걸려요
- 투표 기능을 고치다가 실수로 리마인더 코드를 건드립니다
- 코드 한 줄 고칠 때마다 봇을 껐다 켜야 해요 (그 사이 서버는 봇 없이 방치)
- 나중에 깃허브에 올리거나 남에게 넘길 때 어디서부터 봐야 할지 아무도 몰라요
디스코드 봇은 한 번 만들면 몇 달씩 굴리는 물건이잖아요. 지금 30분 투자해서 구조를 잡아두면 앞으로 몇 시간을 아낍니다 👍
🧩 확장(Extension)과 Cog, 뭐가 다른가요?
둘을 헷갈리는 분이 정말 많아요. 딱 이렇게 정리하면 됩니다.
| 구분 | 정체 | 역할 |
|---|---|---|
| 확장 (Extension) | setup 함수가 있는 파이썬 파일 |
불러오기/내리기 단위 |
| Cog | commands.Cog 를 상속한 클래스 |
명령어·리스너를 담는 묶음 |
공식 문서 표현을 빌리면, 확장은 setup 이라는 진입점을 가진 파이썬 파일이고 이 setup 함수는 반드시 코루틴(async def) 이어야 해요. Cog는 그 안에 들어가는 내용물이고요. Cog 없이 확장만 만들 수도 있지만, 보통은 확장 1개 = Cog 1개 조합으로 씁니다.
💬 이 시리즈에서도
cogs/poll.py파일(확장) 안에Poll클래스(Cog)를 넣는 방식으로 갑니다. 파일 하나 = 기능 하나예요.
📌 준비물
- Python 3.8+
- discord.py 2.x (2026년 8월 기준 최신은 2.7.1)
- python-dotenv — 봇 토큰을 코드에 박지 않기 위해 필수!
pip install -U discord.py python-dotenv설치된 버전 확인은 이렇게 해요.
python -c "import discord; print(discord.__version__)"프로젝트 폴더에 .env 파일도 준비해주세요.
DISCORD_TOKEN=여기에_봇_토큰_붙여넣기
GUILD_ID=테스트용_서버_ID💡
.env는 꼭.gitignore에 넣으세요. 깃허브에 토큰이 올라가면 디스코드가 감지해서 토큰을 바로 폐기해버려요.
📌 1단계 - 폴더 구조부터 잡기
오늘 만들 구조예요. 최종 프로젝트까지 이 모양 그대로 갑니다.
server-bot/
├─ .env # 토큰·서버 ID (git 제외)
├─ .gitignore
├─ requirements.txt
├─ bot.py # 진입점 (Cog 로드만 담당)
├─ config.py # 설정 로딩
└─ cogs/
├─ poll.py # #18 투표 ← 오늘 이사시킬 파일
└─ admin.py # 리로드 등 관리 명령어포인트는 bot.py 가 아주 얇아진다는 거예요. 진입점은 "켜고, Cog 불러오고, 로그인한다" 이 세 가지만 합니다. 실제 기능은 전부 cogs/ 안으로 들어가요.
💬
cogs폴더에__init__.py는 없어도 됩니다. discord.py는 파이썬 import 규칙을 그대로 쓰기 때문에cogs/poll.py는"cogs.poll"처럼 점(.)으로 연결한 이름으로 부르면 돼요.
📌 2단계 - 진입점 bot.py 만들기
핵심은 setup_hook 이에요. 봇이 디스코드에 접속하기 직전, 이벤트가 하나도 오지 않은 안전한 시점에 딱 한 번 불리는 메서드입니다. Cog 로딩은 여기서 하는 게 정석이에요.
import discord
from discord.ext import commands
class ServerBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True
intents.members = True
intents.polls = True
super().__init__(command_prefix="!", intents=intents)
async def setup_hook(self):
for path in COGS_DIR.glob("*.py"):
if path.stem.startswith("_"):
continue
await self.load_extension(f"cogs.{path.stem}")
print(f"✅ cogs.{path.stem} 불러옴")하나씩 뜯어볼게요 💡
commands.Bot상속 : 지금까진bot = commands.Bot(...)로 변수를 만들었죠. 클래스로 감싸면setup_hook같은 메서드를 깔끔하게 덮어쓸 수 있어요.setup_hook:on_ready보다 먼저 실행돼요.on_ready는 재접속할 때마다 여러 번 불릴 수 있어서 Cog 로딩을 넣으면 중복 로드 에러가 납니다. 그래서setup_hook을 씁니다.load_extension("cogs.poll"): 파일 하나를 불러와요..py는 붙이지 않고, 슬래시(/) 대신 점(.) 을 씁니다.path.stem.startswith("_"):_utils.py처럼 밑줄로 시작하는 파일은 건너뛰어요. 공용 함수 파일을 Cog로 착각해서 로드하는 사고를 막습니다.intents.members: #20 온보딩 편에서 쓸 특권 인텐트예요. 미리 켜둘게요.
📌 3단계 - 투표 기능을 Cog로 이사시키기
지난 편 create_poll 함수를 클래스 안으로 옮겨볼게요. 바뀌는 건 딱 3가지입니다.
import datetime
import discord
from discord import app_commands
from discord.ext import commands
NUMBER_EMOJI = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]
class Poll(commands.Cog):
"""투표 관련 명령어 모음"""
def __init__(self, bot: commands.Bot):
self.bot = bot
@app_commands.command(name="투표", description="서버 투표를 만들어요")
async def create_poll(self, interaction: discord.Interaction, 질문: str, 항목: str):
labels = [x.strip() for x in 항목.split(",") if x.strip()]
poll = discord.Poll(question=질문, duration=datetime.timedelta(hours=24))
for index, label in enumerate(labels):
poll.add_answer(text=label, emoji=NUMBER_EMOJI[index])
await interaction.response.send_message("투표를 올렸어요! 📊", ephemeral=True)
await interaction.channel.send(poll=poll)
@commands.Cog.listener()
async def on_poll_vote_add(self, user, answer):
print(f"[투표] {user}님 → '{answer.text}'")
async def setup(bot: commands.Bot):
await bot.add_cog(Poll(bot))하나씩 뜯어볼게요 💡
- 모든 함수에
self가 붙어요 : 클래스 메서드가 됐으니까요. 이걸 빼먹으면interaction자리에self가 들어가서 이상한 에러가 납니다. Cog로 옮길 때 1순위 실수 포인트예요. @bot.tree.command→@app_commands.command: Cog 안에서는bot변수를 쓸 수 없어요. 대신app_commands.command로 선언하면 Cog가 봇에 등록될 때 자동으로 명령어 트리에 붙습니다.@bot.event→@commands.Cog.listener(): Cog 안의 이벤트는 이 데코레이터로 표시해야 인식돼요. 장점도 있어요.@bot.event는 같은 이벤트를 하나만 등록할 수 있지만, 리스너는 여러 Cog에서 같은 이벤트를 동시에 받을 수 있어요.async def setup(bot): 파일 맨 아래 이 함수가 있어야 확장으로 인정받아요.async를 빼먹으면 로드가 실패합니다.self.bot: 다른 곳에서 봇 객체가 필요할 때 쓰려고 저장해둬요. (self.bot.get_channel(...)같은 식)
📌 4단계 - 재시작 없는 핫리로드 만들기
이제 오늘의 하이라이트예요. 코드를 고친 뒤 봇을 끄지 않고 그 파일만 다시 불러오는 명령어를 만듭니다.
@commands.command(name="reload")
@commands.is_owner()
async def reload_cog(self, ctx: commands.Context, name: str):
try:
await self.bot.reload_extension(f"cogs.{name}")
except commands.ExtensionNotLoaded:
await self.bot.load_extension(f"cogs.{name}")
await ctx.send(f"🆕 `{name}` 을 새로 불러왔어요!")
except commands.ExtensionError as error:
await ctx.send(f"❌ 실패: `{type(error).__name__}: {error}`")
else:
await ctx.send(f"🔄 `{name}` 을 다시 불러왔어요!")하나씩 뜯어볼게요 💡
reload_extension: 파일을 내렸다가 다시 올려요. 공식 문서에 따르면 리로드 도중 에러가 나면 아예 없던 일처럼 원래 상태로 되돌립니다. 오타 하나로 봇 전체가 죽는 참사를 막아줘요.@commands.is_owner(): 봇 소유자만 쓸 수 있게 잠급니다. 이거 없으면 서버원 아무나 봇 코드를 리로드할 수 있어요. 절대 빼지 마세요.ExtensionNotLoaded: 아직 한 번도 안 불러온 파일이면 리로드가 아니라load_extension으로 넘어가요. 봇을 켠 뒤에 새로 만든 Cog 파일도 즉시 붙일 수 있습니다.- 슬래시 명령어가 아니라 prefix 명령어 : 관리용이라
!reload poll처럼 씁니다. 슬래시 명령어로 만들면 리로드할 때마다 동기화가 꼬일 수 있어서 관리 명령어는 prefix가 편해요.
⚠️ 주의: 파이썬 코드는 바로 반영되지만, 슬래시 명령어의 이름·설명·옵션을 바꿨다면
bot.tree.sync()를 한 번 더 해줘야 디스코드 쪽에 반영돼요. 아래 전체 코드에!sync명령어도 같이 넣어뒀습니다.
📌 5단계 - Cog가 내려갈 때 뒷정리하기
Cog에는 생명주기 메서드가 있어요. 자주 쓰는 두 개만 짚고 갈게요.
class Poll(commands.Cog):
async def cog_load(self):
print("[Poll] 준비 완료!") # DB 연결, 캐시 로딩 등
async def cog_unload(self):
print("[Poll] 정리했어요") # 타이머 중지, 연결 닫기 등cog_load(): Cog가 봇에 붙을 때 불려요.__init__과 달리async라서await를 쓸 수 있어요. DB 연결처럼 시간이 걸리는 준비는 여기서 합니다.cog_unload(): 내려갈 때 불려요. 리로드할 때마다 실행되니까tasks.loop같은 반복 작업은 여기서 꼭 멈춰주세요. 안 그러면 리로드할 때마다 타이머가 하나씩 늘어나서 알림이 2배, 3배로 옵니다 😱
📌 전체 코드 (복붙 OK)
1) bot.py
# discord.py 2.x (2.7.1 기준) / Python 3.8+
# 디스코드 봇 만들기 #19 - Cog 구조화
import asyncio
import os
from pathlib import Path
import discord
from discord.ext import commands
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
BASE_DIR = Path(__file__).parent
COGS_DIR = BASE_DIR / "cogs"
class ServerBot(commands.Bot):
def __init__(self):
intents = discord.Intents.default()
intents.message_content = True # 특권 인텐트 (개발자 포털에서도 ON)
intents.members = True # 특권 인텐트 (#20 온보딩용 미리 준비)
intents.polls = True # 투표 이벤트용
super().__init__(command_prefix="!", intents=intents)
async def setup_hook(self):
"""봇이 접속하기 직전에 딱 한 번 실행돼요."""
for path in sorted(COGS_DIR.glob("*.py")):
if path.stem.startswith("_"):
continue
try:
await self.load_extension(f"cogs.{path.stem}")
print(f"✅ cogs.{path.stem} 불러옴")
except commands.ExtensionError as error:
print(f"❌ cogs.{path.stem} 실패: {error}")
async def on_ready(self):
print(f"{self.user} 로그인 성공! (Cog {len(self.cogs)}개)")
async def main():
if not TOKEN:
raise SystemExit(".env 에 DISCORD_TOKEN 이 없어요!")
bot = ServerBot()
async with bot:
await bot.start(TOKEN)
if __name__ == "__main__":
asyncio.run(main())2) cogs/poll.py
# discord.py 2.x / Python 3.8+
# #18 투표 기능을 Cog로 옮긴 버전
import datetime
import discord
from discord import app_commands
from discord.ext import commands
NUMBER_EMOJI = ["1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣", "🔟"]
MAX_ANSWERS = 10
MAX_QUESTION_LEN = 300
class Poll(commands.Cog):
"""투표 관련 명령어 모음"""
def __init__(self, bot: commands.Bot):
self.bot = bot
async def cog_load(self):
print("[Poll] 준비 완료!")
async def cog_unload(self):
print("[Poll] 정리했어요")
@app_commands.command(name="투표", description="서버 투표를 만들어요")
@app_commands.describe(
질문="투표에 띄울 질문 (최대 300자)",
항목="선택지를 쉼표(,)로 구분해서 입력 (2~10개)",
시간="진행 시간 (시간 단위, 기본 24)",
)
async def create_poll(
self,
interaction: discord.Interaction,
질문: str,
항목: str,
시간: int = 24,
):
labels = [x.strip() for x in 항목.split(",") if x.strip()]
if not 2 <= len(labels) <= MAX_ANSWERS:
await interaction.response.send_message(
f"선택지는 2~{MAX_ANSWERS}개여야 해요! (지금 {len(labels)}개)",
ephemeral=True,
)
return
if len(질문) > MAX_QUESTION_LEN:
await interaction.response.send_message(
f"질문은 {MAX_QUESTION_LEN}자까지만 가능해요!", ephemeral=True
)
return
poll = discord.Poll(
question=질문,
duration=datetime.timedelta(hours=시간),
)
for index, label in enumerate(labels):
poll.add_answer(text=label, emoji=NUMBER_EMOJI[index])
await interaction.response.send_message("투표를 올렸어요! 📊", ephemeral=True)
await interaction.channel.send(poll=poll)
@commands.Cog.listener()
async def on_poll_vote_add(self, user, answer):
print(f"[투표] {user}님 → '{answer.text}' (현재 {answer.vote_count}표)")
async def setup(bot: commands.Bot):
await bot.add_cog(Poll(bot))3) cogs/admin.py
# discord.py 2.x / Python 3.8+
# 봇 관리용 명령어 (소유자 전용)
import discord
from discord.ext import commands
class Admin(commands.Cog):
"""봇 소유자 전용 관리 명령어"""
def __init__(self, bot: commands.Bot):
self.bot = bot
@commands.command(name="reload")
@commands.is_owner()
async def reload_cog(self, ctx: commands.Context, name: str):
"""!reload poll → cogs/poll.py 를 다시 불러와요"""
try:
await self.bot.reload_extension(f"cogs.{name}")
except commands.ExtensionNotLoaded:
try:
await self.bot.load_extension(f"cogs.{name}")
except commands.ExtensionError as error:
await ctx.send(f"❌ 실패: `{type(error).__name__}: {error}`")
else:
await ctx.send(f"🆕 `{name}` 을 새로 불러왔어요!")
except commands.ExtensionError as error:
await ctx.send(f"❌ 실패: `{type(error).__name__}: {error}`")
else:
await ctx.send(f"🔄 `{name}` 을 다시 불러왔어요!")
@commands.command(name="cogs")
@commands.is_owner()
async def list_cogs(self, ctx: commands.Context):
"""지금 올라가 있는 Cog 목록을 보여줘요"""
names = "\n".join(f"· {name}" for name in self.bot.cogs) or "(없음)"
await ctx.send(f"**불러온 Cog {len(self.bot.cogs)}개**\n{names}")
@commands.command(name="sync")
@commands.is_owner()
async def sync_tree(self, ctx: commands.Context):
"""슬래시 명령어를 이 서버에 즉시 동기화해요"""
self.bot.tree.copy_global_to(guild=ctx.guild)
synced = await self.bot.tree.sync(guild=ctx.guild)
await ctx.send(f"🌳 슬래시 명령어 {len(synced)}개 동기화 완료!")
@reload_cog.error
async def reload_error(self, ctx: commands.Context, error):
if isinstance(error, commands.NotOwner):
await ctx.send("이 명령어는 봇 소유자만 쓸 수 있어요 🔒")
async def setup(bot: commands.Bot):
await bot.add_cog(Admin(bot))📌 실행 결과
python bot.py 로 실행하면 터미널에 이렇게 찍혀요.
[Poll] 준비 완료!
✅ cogs.admin 불러옴
✅ cogs.poll 불러옴
MojangBot#1234 로그인 성공! (Cog 2개)이제 cogs/poll.py 를 열어서 응답 문구를 아무거나 바꿔보세요. 그리고 봇을 끄지 말고 디스코드에 이렇게 쳐봅니다.
!reload poll[Poll] 정리했어요
[Poll] 준비 완료!
🔄 poll 을 다시 불러왔어요!바꾼 코드가 즉시 적용돼요. 봇은 계속 온라인 상태고, 서버원들은 아무것도 눈치채지 못합니다 😎

📌 자주 나는 에러
1. ExtensionFailed: Extension 'cogs.poll' raised an error: TypeError: object NoneType can't be used in 'await' expression
- 원인 :
setup함수를def setup(bot):처럼 일반 함수로 만들었어요. - 해결 :
async def setup(bot):로 고치고 안에서await bot.add_cog(...)를 쓰세요. discord.py 2.0부터setup과add_cog둘 다 코루틴입니다.
2. ExtensionNotFound: Extension 'cogs/poll.py' could not be loaded
- 원인 : 경로를 파일 경로처럼 썼어요.
- 해결 :
"cogs.poll"처럼 점으로 연결하고.py는 빼세요. 실행도 반드시server-bot폴더 안에서python bot.py로 해야 해요. 다른 위치에서 실행하면cogs를 못 찾습니다.
3. NoEntryPointError: Extension 'cogs.poll' has no 'setup' function
- 원인 : 파일 맨 아래
setup함수를 안 넣었어요. Cog 클래스만 있으면 확장으로 인정되지 않습니다. - 해결 : 파일 끝에
async def setup(bot): await bot.add_cog(Poll(bot))를 추가하세요.
4. ExtensionAlreadyLoaded
- 원인 : 같은 확장을 두 번 불러왔어요.
on_ready안에서 로딩하면 재접속할 때마다 이 에러가 납니다. - 해결 : 로딩은
setup_hook에서만 하세요. 다시 불러올 땐load_extension이 아니라reload_extension을 씁니다.
5. 슬래시 명령어가 Cog로 옮긴 뒤 안 보여요
- 원인 : 명령어 자체는 등록됐지만 디스코드에 동기화가 안 됐어요. 전역 동기화는 반영이 느릴 수 있어요.
- 해결 : 위 코드의
!sync를 한 번 실행하세요.copy_global_to(guild=...)+sync(guild=...)조합이라 테스트 서버에는 즉시 반영됩니다.
6. TypeError: create_poll() missing 1 required positional argument
- 원인 : Cog로 옮기면서
self를 안 붙였어요. - 해결 : Cog 안의 모든 명령어·리스너 함수는 첫 번째 인자가
self여야 합니다.async def create_poll(self, interaction, ...)
7. !reload 를 쳐도 봇이 아무 반응이 없어요
- 원인 : prefix 명령어는 Message Content 인텐트가 필요해요.
- 해결 : 코드에
intents.message_content = True를 넣고, 디스코드 개발자 포털 → Bot → Privileged Gateway Intents 에서도 켜주세요.
📌 마무리
오늘은 봇 코드를 확장(Extension) + Cog 구조로 쪼개고, setup_hook 으로 자동 로딩하고, !reload 로 재시작 없는 핫리로드까지 만들어봤어요 🙌
오늘의 요약
- 확장 =
setup이 있는 파일, Cog = 명령어를 담은 클래스 - 로딩은
setup_hook에서 (on_ready에서 하면 중복 로드!) - Cog 안에서는
@app_commands.command+@commands.Cog.listener() - 파일 맨 아래
async def setup(bot)필수, 모든 메서드에self - 수정 후엔
!reload 파일이름, 명령어 모양이 바뀌었으면!sync까지
다음 #20편에서는 오늘 만든 구조 위에 첫 번째 기능 파일을 얹어볼게요. 바로 자동 환영 메시지와 온보딩이에요. 새 멤버가 들어오면 환영 카드를 띄우고, 규칙 동의 버튼을 누르면 자동으로 역할을 부여하는 cogs/onboarding.py 를 만듭니다. 서버 관리자라면 제일 먼저 원하는 기능이죠? 기대해주세요! 🙌
💬 오늘부터 코드가 여러 파일로 나뉘니까,
server-bot폴더째로 잘 보관해두세요. #27 최종 프로젝트에서 이 폴더가 그대로 완성본이 됩니다!
📚 디스코드 봇 만들기 시리즈
- #1 ~ #12 — 봇 만들기 기초부터 쿨다운·권한까지
- #13 역할·채널 자동화
- #14 데이터 저장하기 (JSON vs SQLite)
- #15 슬래시 명령어 그룹(Group)
- #16 리마인더·알림 기능
- #17 RAG로 문서 기반 답변봇 만들기
- #18 투표(Poll) 기능 만들기
- #19 Cog로 코드 쪼개기 ← 지금 이 글!
- #20 자동 환영 메시지·온보딩 (예정)
연재 · 32편 중 19번째
디스코드 봇 만들기
- #1디스코드 봇 만들기 - Slash Command 완전 정복!
- #2Slash Command에 옵션(Arguments) 추가하기!
- #3Slash Command에 서브 커맨드(Subcommand) 넣기!
- #4Slash Command로 임베드 전송하기!
- #5버튼(Button) 상호작용 구현하기!
- #6드롭다운(Select Menu) 메뉴 구현하기!
- #7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)
- #8임베드(Embed) 이미지·필드로 꾸미기 (파이썬 예제)
- #9AI(Claude·ChatGPT API) 연동하기! (파이썬 예제)
- #10다국어(i18n) 지원하기! (파이썬 예제)
- #11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)
- #12명령어 쿨다운(Cooldown)·권한(Permission) 관리하기! (파이썬 예제)
- #13역할·채널 자동화로 서버 관리하기! (파이썬 예제)
- #14데이터 저장하기! JSON vs SQLite (파이썬 예제)
- #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제)
- #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제)
- #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제)
- #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제)
- #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제)지금 읽는 글
- #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제)
- #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제)
- #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제)
- #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제)
- #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제)
- #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제)
- #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제)
- #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제)
- #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제)
- #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제)
- #30투표·설문(Poll) 봇 만들기! 네이티브 투표 + 버튼으로 실시간 집계까지 (파이썬 예제) 🗳️
- #32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️
- #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉
