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

디스코드 봇 만들기#15슬래시 명령어 그룹(Group)으로 명령어 정리하기! (파이썬 예제)

목차11개 항목

안녕하세요! Mojangcam입니다 😎

지난 #14편에서는 JSON과 SQLite로 봇에게 기억력을 만들어줬어요. 그런데 시리즈를 따라오면서 기능을 하나씩 붙이다 보니, 어느새 명령어가 열댓 개를 훌쩍 넘지 않았나요? 디스코드에서 /만 입력하면 명령어가 우르르 쏟아져서 정작 필요한 걸 찾기 힘들어지는 시점이 옵니다.

그래서 이번 #15편에서는 슬래시 명령어를 폴더처럼 묶어서 정리하는 슬래시 명령어 그룹(Group)을 배워볼게요. /설정 환영채널, /설정 알림 켜기처럼 유명한 봇들이 쓰는 깔끔한 명령어 구조를 우리 봇에도 달아줍니다!

🧠 왜 그룹이 필요할까요?

명령어가 5개일 때는 아무 문제가 없어요. 그런데 서버 관리 봇을 만들다 보면 /환영채널설정, /인사말설정, /알림켜기, /알림끄기… 이런 식으로 이름이 점점 길고 지저분해져요. 이름이 겹칠까 봐 앞에 접두어를 붙이기 시작하면 이미 신호가 온 거예요.

그룹을 쓰면 관련 명령어가 한 지붕 아래로 들어가요. 유저가 /설정까지만 입력해도 하위 명령어 목록이 자동완성으로 좌르륵 떠서, 봇에 어떤 기능이 있는지 한눈에 보인다는 것도 큰 장점이에요. 음악 봇의 /음악 재생, /음악 정지 같은 구조가 다 이렇게 만들어진 거랍니다.

🧩 그룹 구조, 딱 3가지만 기억하세요

코드
/설정 보기          ← 그룹 + 하위 명령어 (2단)
/설정 알림 켜기     ← 그룹 + 하위 그룹 + 하위 명령어 (3단)
규칙 내용
그룹 자체는 실행 불가 /설정만 입력해서 실행하는 건 안 돼요. 그룹은 폴더일 뿐, 실제 동작은 항상 하위 명령어가 해요
중첩은 1단계까지 그룹 → 하위 그룹 → 명령어(3단)가 최대예요. 하위 그룹 안에 또 그룹은 못 넣어요
하위 명령어 최대 25개 그룹 하나에 하위 명령어·하위 그룹을 합쳐 25개까지 넣을 수 있어요

이름 규칙도 하나 짚고 갈게요. 슬래시 명령어 이름은 공백 불가·영문은 소문자만 가능해요. 한글 이름은 자유롭게 쓸 수 있어서 /설정 환영채널처럼 만들 수 있답니다.

📌 준비물

  • Python 3.8 이상
  • discord.py 2.x (최신 2.x 버전이면 돼요)
  • 봇 토큰과 서버에 초대된 봇 (시리즈 앞편에서 만든 그대로!)
cmake
pip install -U discord.py

이번 편은 슬래시 명령어만 쓰기 때문에 Message Content Intent는 켜지 않아도 돼요.

📌 1단계 - 가장 간단한 그룹 만들기

먼저 app_commands.Group 인스턴스를 만들고, 거기에 명령어를 붙이는 방식부터 볼게요.

haskell
# discord.py 2.x
import discord
from discord import app_commands
from discord.ext import commands

intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)

# 1) 그룹(폴더) 만들기
인사 = app_commands.Group(name="인사", description="인사 명령어 모음")

# 2) 그룹에 하위 명령어 붙이기
@인사.command(name="안녕", description="반갑게 인사해요")
async def hello(interaction: discord.Interaction):
    await interaction.response.send_message("안녕하세요! 👋")

# 3) 그룹을 봇에 등록하기
bot.tree.add_command(인사)

하나씩 뜯어볼게요 💡

  • app_commands.Group(name=..., description=...) : 명령어를 담을 폴더를 만들어요. name이 / 뒤에 오는 그룹 이름이 됩니다.
  • @인사.command() : 평소 쓰던 @bot.tree.command() 대신 그룹 이름.command()를 쓰면 그 그룹의 하위 명령어가 돼요.
  • bot.tree.add_command(인사) : 명령어 하나하나가 아니라 그룹을 통째로 트리에 등록해요. 하위 명령어들은 자동으로 따라 들어갑니다.

이제 /인사 안녕으로 실행할 수 있어요. 참 쉽죠? 그런데 명령어가 많아지면 이 방식은 코드가 흩어져서 관리가 어려워요. 그래서 실전에서는 클래스 방식을 씁니다.

📌 2단계 - 클래스로 그룹 만들기 (실전 방식)

app_commands.Group을 상속받은 클래스를 만들면, 관련 명령어를 한 클래스 안에 깔끔하게 모을 수 있어요. 서버 관리 봇의 단골 기능인 설정 그룹을 만들어볼게요.

코드
# discord.py 2.x
class Settings(app_commands.Group, name="설정", description="서버 설정 명령어 모음"):

    @app_commands.command(name="환영채널", description="환영 메시지를 보낼 채널을 지정해요")
    @app_commands.describe(채널="환영 메시지를 보낼 텍스트 채널")
    async def welcome_channel(self, interaction: discord.Interaction, 채널: discord.TextChannel):
        config["welcome"] = 채널.id
        await interaction.response.send_message(
            f"✅ 환영 채널을 {채널.mention} 로 설정했어요!", ephemeral=True
        )

bot.tree.add_command(Settings())

하나씩 뜯어볼게요 💡

  • class Settings(app_commands.Group, name="설정", ...) : 클래스 정의에 name과 description을 함께 적어요. 이 클래스가 곧 /설정 그룹이 됩니다.
  • @app_commands.command() : 클래스 안에서는 이 데코레이터로 하위 명령어를 만들어요. 메서드라서 첫 번째 인자에 self가 꼭 들어가야 해요! (1단계 방식과 다른 점이에요)
  • @app_commands.describe(채널=...) : 명령어 입력창에서 파라미터 옆에 뜨는 설명이에요. 유저 친화도가 확 올라가요.
  • ephemeral=True : 응답을 명령어를 친 본인에게만 보여줘요. 설정 명령어에 딱이에요.
  • bot.tree.add_command(Settings()) : 클래스를 인스턴스로 만들어서(괄호!) 등록해요.

📌 3단계 - 그룹 속 그룹으로 3단 구조 만들기

/설정 알림 켜기처럼 한 단계 더 들어가는 구조는, 클래스 안에 app_commands.Group을 클래스 속성으로 선언하면 돼요.

코드
# discord.py 2.x
class Settings(app_commands.Group, name="설정", description="서버 설정 명령어 모음"):

    # 그룹 속 그룹: /설정 알림 ...
    알림 = app_commands.Group(name="알림", description="알림 기능을 켜고 꺼요")

    @알림.command(name="켜기", description="알림을 켜요")
    async def alarm_on(self, interaction: discord.Interaction):
        await interaction.response.send_message("🔔 알림을 켰어요!")

    @알림.command(name="끄기", description="알림을 꺼요")
    async def alarm_off(self, interaction: discord.Interaction):
        await interaction.response.send_message("🔕 알림을 껐어요!")

알림 = app_commands.Group(...) 이 한 줄이 핵심이에요. 클래스 속성으로 선언한 그룹은 자동으로 하위 그룹이 되고, @알림.command()로 그 안에 명령어를 넣으면 /설정 알림 켜기 완성! 단, 여기서 또 한 단계 더 들어가는 건 디스코드 규칙상 불가능하다는 것, 기억하시죠?

📌 전체 코드 (복붙 OK)

TOKEN만 바꾸면 바로 돌아가는 완성본이에요. /설정 환영채널, /설정 인사말, /설정 보기, /설정 알림 켜기/끄기까지 서버 설정 봇 하나가 통째로 들어있어요.

Python
# discord.py 2.x / Python 3.8+
import discord
from discord import app_commands
from discord.ext import commands

TOKEN = "여기에_봇_토큰"

intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)

# 서버별 설정을 담아둘 임시 저장소 (봇을 끄면 사라져요! 저장은 #14편 참고)
config = {}

def get_config(guild_id):
    """서버별 설정 dict를 가져오고, 없으면 기본값으로 만들어요."""
    return config.setdefault(
        guild_id, {"welcome": None, "greeting": "어서 오세요!", "alarm": False}
    )

class Settings(app_commands.Group, name="설정", description="서버 설정 명령어 모음"):

    @app_commands.command(name="환영채널", description="환영 메시지를 보낼 채널을 지정해요")
    @app_commands.describe(채널="환영 메시지를 보낼 텍스트 채널")
    async def welcome_channel(self, interaction: discord.Interaction, 채널: discord.TextChannel):
        get_config(interaction.guild_id)["welcome"] = 채널.id
        await interaction.response.send_message(
            f"✅ 환영 채널을 {채널.mention} 로 설정했어요!", ephemeral=True
        )

    @app_commands.command(name="인사말", description="환영 인사말 문구를 바꿔요")
    @app_commands.describe(문구="새 환영 인사말")
    async def greeting(self, interaction: discord.Interaction, 문구: str):
        get_config(interaction.guild_id)["greeting"] = 문구
        await interaction.response.send_message(
            f"✅ 인사말을 바꿨어요: {문구}", ephemeral=True
        )

    @app_commands.command(name="보기", description="현재 서버 설정을 확인해요")
    async def show(self, interaction: discord.Interaction):
        c = get_config(interaction.guild_id)
        channel = f"<#{c['welcome']}>" if c["welcome"] else "미설정"
        embed = discord.Embed(title="⚙️ 현재 서버 설정", color=0x5865F2)
        embed.add_field(name="환영 채널", value=channel, inline=True)
        embed.add_field(name="인사말", value=c["greeting"], inline=True)
        embed.add_field(name="알림", value="켜짐 🔔" if c["alarm"] else "꺼짐 🔕", inline=True)
        await interaction.response.send_message(embed=embed, ephemeral=True)

    # ── 그룹 속 그룹: /설정 알림 켜기·끄기 ──
    알림 = app_commands.Group(name="알림", description="알림 기능을 켜고 꺼요")

    @알림.command(name="켜기", description="알림을 켜요")
    async def alarm_on(self, interaction: discord.Interaction):
        get_config(interaction.guild_id)["alarm"] = True
        await interaction.response.send_message("🔔 알림을 켰어요!", ephemeral=True)

    @알림.command(name="끄기", description="알림을 꺼요")
    async def alarm_off(self, interaction: discord.Interaction):
        get_config(interaction.guild_id)["alarm"] = False
        await interaction.response.send_message("🔕 알림을 껐어요!", ephemeral=True)

bot.tree.add_command(Settings())

@bot.event
async def on_ready():
    synced = await bot.tree.sync()
    print(f"✅ {bot.user} 로 로그인! 슬래시 명령어 {len(synced)}개를 동기화했어요.")

bot.run(TOKEN)

하나씩 뜯어볼게요 💡

  • config + get_config() : 서버(guild)마다 설정을 따로 저장하는 구조예요. 지금은 메모리에만 있어서 봇을 재시작하면 초기화돼요. #14편의 JSON/SQLite 저장을 붙이면 완벽해집니다.
  • interaction.guild_id : 명령어가 실행된 서버의 ID예요. 서버별로 설정을 나눠 담는 열쇠로 썼어요.
  • bot.tree.sync() : 만든 명령어를 디스코드 서버에 등록(동기화) 하는 단계예요. 이걸 빼먹으면 명령어가 아예 안 보여요!

📌 실행 결과

봇을 켜고 디스코드에서 확인해봐요.

  1. 채팅창에 /설정 까지만 입력 → 하위 명령어(환영채널·인사말·보기·알림)가 자동완성 목록으로 떠요.
  2. /설정 환영채널 선택 → 채널 선택 UI가 뜨고, 고르면 "✅ 환영 채널을 #일반 로 설정했어요!" (나에게만 보임)
  3. /설정 알림 켜기 → "🔔 알림을 켰어요!" — 3단 구조도 자연스럽게 동작해요.
  4. /설정 보기 → 방금 바꾼 설정이 임베드로 정리되어 나와요.

📌 자주 나는 에러

1. 명령어가 디스코드에 안 보여요

원인: tree.sync()를 안 했거나, 글로벌 동기화 반영이 아직 안 됐어요. 글로벌 명령어는 반영까지 몇 분에서 최대 1시간쯤 걸릴 수 있어요.

해결: on_ready에서 sync가 실행됐는지 확인하고, 디스코드 클라이언트를 Ctrl+R로 새로고침해보세요. 테스트 중이라면 아래처럼 내 서버에만 즉시 반영할 수 있어요.

Python
# discord.py 2.x
MY_GUILD = discord.Object(id=내_서버_ID)

@bot.event
async def on_ready():
    bot.tree.copy_global_to(guild=MY_GUILD)
    await bot.tree.sync(guild=MY_GUILD)   # 내 서버에는 즉시 반영!

2. CommandAlreadyRegistered 에러

원인: 같은 이름의 그룹이나 명령어를 두 번 등록했어요. add_command를 중복 호출했거나, 그룹 이름이 기존 명령어와 겹치는 경우예요.

해결: bot.tree.add_command(Settings())가 한 번만 실행되는지, 그룹 이름이 다른 명령어와 안 겹치는지 확인하세요.

3. ValueError: groups can only be nested at most one level

원인: 그룹 속 그룹 안에 또 그룹을 넣으려고 했어요. 디스코드 규칙상 중첩은 1단계까지예요.

해결: 3단(/설정 알림 켜기)을 초과하는 구조는 만들 수 없어요. 명령어 설계를 2~3단으로 다시 정리하세요.

4. TypeError: ... missing 1 required positional argument (self 관련)

원인: 1단계(모듈 레벨) 방식과 2단계(클래스) 방식을 섞었어요. 클래스 밖 함수에 self를 넣었거나, 클래스 안 메서드에서 self를 빼먹은 거예요.

해결: 클래스 안 메서드에는 self 필수, 클래스 밖 함수에는 self 금지! 이것만 기억하면 돼요.

💡 보너스 팁

  • 그룹 전체에 권한 걸기 : 설정 명령어는 관리자만 쓰게 해야겠죠? 클래스 정의에 한 줄만 추가하면 하위 명령어 전체에 적용돼요. #12편의 권한 관리와 함께 보면 좋아요.

    코드
    # discord.py 2.x
    class Settings(
        app_commands.Group,
        name="설정",
        description="서버 설정 명령어 모음",
        default_permissions=discord.Permissions(manage_guild=True),  # 서버 관리 권한 필요
        guild_only=True,  # DM에서는 못 쓰게
    ):
  • 명령어 이름 짓기 : 그룹 이름은 설정·음악처럼 명사 폴더, 하위 명령어는 보기·켜기처럼 짧은 동작으로 지으면 자동완성 목록이 문장처럼 읽혀서 깔끔해요.

  • 코드 파일이 너무 길어졌다면 : 그룹을 파일별로 나눠 관리하는 Cog(코그)라는 도구도 있어요. 시리즈 후반에 봇 구조 정리 편에서 제대로 다뤄볼게요!

📌 마무리

오늘은 슬래시 명령어를 폴더처럼 정리하는 그룹(Group)을 배웠어요. app_commands.Group 인스턴스로 간단하게, 클래스 상속으로 깔끔하게, 클래스 속성 그룹으로 3단 구조까지 — 이제 명령어가 아무리 많아져도 무섭지 않아요! 그룹 자체는 실행 불가, 중첩은 1단계까지, 하위 명령어는 25개까지라는 규칙 세 가지만 기억해주세요.

다음 편에서는 지정한 시간에 봇이 먼저 말을 거는 리마인더·알림 기능을 만들어볼게요. 기대해주세요! 🙌

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

연재 · 32편 중 15번째

디스코드 봇 만들기

  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 실전 기록 블로그