카테고리: 디스코드 봇 만들기

디스코드 봇 만들기#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 로딩은 여기서 하는 게 정석이에요.

Python
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가지입니다.

Python
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단계 - 재시작 없는 핫리로드 만들기

이제 오늘의 하이라이트예요. 코드를 고친 뒤 봇을 끄지 않고 그 파일만 다시 불러오는 명령어를 만듭니다.

Python
@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에는 생명주기 메서드가 있어요. 자주 쓰는 두 개만 짚고 갈게요.

Python
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

Python
# 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

Python
# 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

Python
# 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 최종 프로젝트에서 이 폴더가 그대로 완성본이 됩니다!


📚 디스코드 봇 만들기 시리즈

연재 · 32편 중 19번째

디스코드 봇 만들기

  1. #1디스코드 봇 만들기 - Slash Command 완전 정복!
  2. #2Slash Command에 옵션(Arguments) 추가하기!
  3. #3Slash Command에 서브 커맨드(Subcommand) 넣기!
  4. #4Slash Command로 임베드 전송하기!
  5. #5버튼(Button) 상호작용 구현하기!
  6. #6드롭다운(Select Menu) 메뉴 구현하기!
  7. #7모달(Modal)로 입력 받기 구현하기 (파이썬 예제)
  8. #8임베드(Embed) 이미지·필드로 꾸미기 (파이썬 예제)
  9. #9AI(Claude·ChatGPT API) 연동하기! (파이썬 예제)
  10. #10다국어(i18n) 지원하기! (파이썬 예제)
  11. #11플라스크(Flask)로 웹 대시보드 만들기! (파이썬 예제)
  12. #12명령어 쿨다운(Cooldown)·권한(Permission) 관리하기! (파이썬 예제)
  13. #13역할·채널 자동화로 서버 관리하기! (파이썬 예제)
  14. #14데이터 저장하기! JSON vs SQLite (파이썬 예제)
  15. #15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제)
  16. #16리마인더·알림 기능 만들기! 봇이 먼저 말 걸게 하기 (파이썬 예제)
  17. #17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제)
  18. #18투표(Poll) 기능 만들기! 다수결로 정하는 서버 규칙 (파이썬 예제)
  19. #19Cog로 코드 쪼개기! 1000줄 봇도 안 더러워지는 구조 (파이썬 예제)지금 읽는 글
  20. #20자동 환영 메시지·온보딩 만들기! 규칙 동의 버튼으로 자동 역할 부여 (파이썬 예제)
  21. #21키워드 자동응답·금지어 필터 만들기! 경고 누적으로 자동 제재까지 (파이썬 예제)
  22. #22슬래시 명령어 자동완성(Autocomplete) 만들기! 금지어를 골라서 추가·삭제 (파이썬 예제)
  23. #23aiosqlite로 비동기 DB 제대로 설계하기! JSON 탈출 (파이썬 예제)
  24. #24채널 난독화(Channel Obfuscation) 대응하기! 11/16부터 안 보이는 채널이 생겨요 (파이썬 예제)
  25. #25봇이 죽지 않게! 에러 핸들링·로깅 체계 잡기 (파이썬 예제)
  26. #26티켓(Ticket) 문의 시스템 만들기! 버튼 하나로 1:1 상담 채널 자동 생성 (파이썬 예제)
  27. #27티켓 담당자 배정·우선순위 만들기! 스태프가 여러 명이어도 안 꼬이게 (파이썬 예제)
  28. #28레벨·경험치(XP) 시스템 만들기! 채팅할수록 레벨업, 랭킹판까지 (파이썬 예제)
  29. #29화폐·상점(Economy) 시스템 만들기! 코인 벌어서 아이템 사기 (파이썬 예제)
  30. #30투표·설문(Poll) 봇 만들기! 네이티브 투표 + 버튼으로 실시간 집계까지 (파이썬 예제) 🗳️
  31. #32서버 로그·감사(Audit Log) 채널 만들기! 메시지 삭제·수정·입퇴장·추방까지 자동 기록 (파이썬 예제) 🕵️
  32. #33임시 음성방 확장, 파티방 만들기! 방장 위임·강퇴·초대 전용(비공개) 방 + 전용 채팅방 자동 생성 (파이썬 예제) 🎉
전체 32편 보기접기

글쓴이

Mojangcam

직접 코드를 짜고, 서버를 돌리고, 막힌 에러를 해결하며 배운 것들을 기록하는 개발·IT 실전 기록 블로그