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

디스코드 봇 만들기#13역할·채널 자동화로 서버 관리하기! (파이썬 예제)

목차9개 항목

안녕하세요! Mojangcam입니다 😎

지난 #12편에서는 명령어 쿨다운과 권한으로 봇을 안전하게 지키는 법을 배웠어요. 이번 #13편에서는 한 걸음 더 나아가, 서버 관리자의 반복 노동을 봇에게 통째로 넘겨볼게요. 오늘 만들 자동화는 딱 세 가지예요.

  • 새 멤버가 들어오면 자동으로 역할 지급 (환영 역할)
  • 버튼 한 번으로 원하는 역할 셀프 지급 (역할 버튼 패널)
  • 음성 채널에 입장하면 개인 방 자동 생성 (Join to Create)

서버가 커질수록 "새로 온 사람한테 역할 달아주기", "게임할 사람 역할 나눠주기", "회의방 만들어주기" 같은 일이 끝없이 반복돼요. 이걸 사람이 손으로 하면 밤새도 못 끝내죠. 봇에게 맡기면 24시간 알아서 처리해줍니다. 그럼 시작해볼게요! 🙌

📌 준비물

먼저 개발 환경부터 챙길게요.

  • Python 3.9 이상 (discord.py 2.7은 3.9+ 필요)
  • discord.py 2.x (2026년 7월 기준 최신 안정 버전 2.7.1)

라이브러리 설치는 터미널에서 한 줄이면 끝나요.

터미널
pip install -U discord.py

그리고 이번 편은 프리빌리지드 인텐트(Privileged Intents) 설정이 꼭 필요해요. 새 멤버 입장을 감지하려면 Server Members Intent를 켜야 하거든요.

  1. 디스코드 개발자 포털 접속 → 내 봇 선택
  2. 왼쪽 Bot 탭 → Privileged Gateway Intents
  3. Server Members Intent 켜기 ✅ (메시지 명령어를 쓸 거면 Message Content Intent도 켜기)

마지막으로 서버에서 봇 역할의 권한도 확인해요. 자동화가 동작하려면 봇에게 아래 권한이 있어야 해요.

  • 역할 관리(Manage Roles) — 역할을 붙이고 떼기
  • 채널 관리(Manage Channels) — 음성 채널 만들고 지우기
  • 멤버 이동(Move Members) — 만든 방으로 멤버 옮기기

💡 가장 중요한 포인트! 봇이 부여할 역할은 반드시 봇 자신의 역할보다 아래에 있어야 해요. 서버 설정 → 역할 목록에서 봇 역할을 위쪽으로 끌어올려 주세요. 이걸 놓치면 뒤에서 볼 Forbidden 에러가 납니다.

📌 1단계 - 새 멤버가 오면 역할 자동 지급

먼저 가장 기본이 되는 자동 환영 역할부터 만들어요. 누군가 서버에 들어오는 순간 on_member_join 이벤트가 실행돼요. 여기서 역할을 찾아 붙여주면 끝이에요.

Python
import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.members = True          # 멤버 입장 감지 (프리빌리지드 인텐트)
intents.message_content = True  # 접두사 명령어용

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

AUTO_ROLE_NAME = "새싹"   # 입장 시 자동으로 줄 역할 이름

@bot.event
async def on_member_join(member: discord.Member):
    role = discord.utils.get(member.guild.roles, name=AUTO_ROLE_NAME)
    if role is not None:
        await member.add_roles(role)
        print(f"👋 {member.name} 님에게 '{role.name}' 역할을 줬어요!")

하나씩 뜯어볼게요 💡

  • intents.members = True : 멤버 입장·퇴장을 감지하는 스위치예요. 이게 꺼져 있으면 on_member_join이 아예 실행되지 않아요.
  • on_member_join(member) : 새 멤버가 들어올 때마다 자동 호출되는 이벤트예요. member에 들어온 사람 정보가 담겨요.
  • discord.utils.get(member.guild.roles, name=...) : 서버 역할 목록에서 이름이 일치하는 역할을 찾아줘요. 없으면 None을 돌려주니 꼭 확인해요.
  • await member.add_roles(role) : 찾은 역할을 그 멤버에게 붙여줘요. 핵심 한 줄이죠!

이제 "새싹"이라는 역할을 서버에 미리 만들어두면, 새 멤버는 들어오자마자 새싹 역할을 달고 등장해요. 🌱

📌 2단계 - 버튼 하나로 역할 셀프 지급

예전 #5 버튼(Button) 편에서 버튼을 배웠죠? 이번엔 그 버튼으로 멤버가 스스로 역할을 고르는 패널을 만들어요. 관리자가 일일이 역할을 달아줄 필요가 없어져요.

여기서 꼭 알아야 할 개념이 영구 View(Persistent View) 예요. 봇을 재시작하면 보통 버튼이 먹통이 되는데, 아래 두 조건을 지키면 재시작 후에도 버튼이 계속 동작해요.

  • View의 timeout=None
  • 모든 버튼에 custom_id 지정
Python
# 버튼 역할 목록 (custom_id, 역할이름, 버튼라벨, 이모지)
BUTTON_ROLES = [
    ("role_notice", "공지알림", "공지 알림", "🔔"),
    ("role_game",   "게임메이트", "게임 메이트", "🎮"),
    ("role_study",  "스터디원",  "스터디원",   "📚"),
]

class RoleButton(discord.ui.Button):
    def __init__(self, custom_id, role_name, label, emoji):
        super().__init__(
            label=label, emoji=emoji,
            style=discord.ButtonStyle.secondary,
            custom_id=custom_id,        # 영구 View 는 custom_id 필수!
        )
        self.role_name = role_name

    async def callback(self, interaction: discord.Interaction):
        role = discord.utils.get(interaction.guild.roles, name=self.role_name)
        if role is None:
            await interaction.response.send_message(
                f"❌ '{self.role_name}' 역할이 없어요. 먼저 서버에 만들어 주세요!",
                ephemeral=True)
            return
        if role in interaction.user.roles:
            await interaction.user.remove_roles(role)
            msg = f"'{role.name}' 역할을 뺐어요. 👋"
        else:
            await interaction.user.add_roles(role)
            msg = f"'{role.name}' 역할을 드렸어요! 🎉"
        await interaction.response.send_message(msg, ephemeral=True)

class RoleView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)   # 영구 View 는 timeout=None!
        for custom_id, role_name, label, emoji in BUTTON_ROLES:
            self.add_item(RoleButton(custom_id, role_name, label, emoji))

하나씩 뜯어볼게요 💡

  • custom_id : 버튼마다 붙이는 고유 이름표예요. 재시작 후 봇이 "아, 이 버튼은 그 버튼!" 하고 알아보게 해줘요. 영구 View의 필수 조건이에요.
  • callback(self, interaction) : 버튼을 눌렀을 때 실행되는 함수예요. interaction.user가 버튼을 누른 사람이에요.
  • role in interaction.user.roles : 이미 그 역할이 있으면 떼고(remove_roles), 없으면 붙여줘요(add_roles). 버튼 하나로 토글이 되는 거죠.
  • ephemeral=True : "나만 보이는 메시지"예요. 다른 사람 화면을 어지럽히지 않고 조용히 알려줘요.

이제 이 패널을 채팅에 띄우는 명령어와, 재시작 후에도 버튼이 살아있게 하는 코드를 더해요.

Python
class MyBot(commands.Bot):
    def __init__(self):
        super().__init__(command_prefix="!", intents=intents)

    async def setup_hook(self):
        self.add_view(RoleView())   # 재시작 후 버튼 되살리기

bot = MyBot()

@bot.command(name="역할판")
@commands.has_permissions(administrator=True)
async def role_panel(ctx: commands.Context):
    embed = discord.Embed(
        title="🎭 역할 받기",
        description="버튼을 누르면 역할을 드려요! 한 번 더 누르면 역할이 빠져요.",
        color=0x5865F2,
    )
    await ctx.send(embed=embed, view=RoleView())
  • setup_hook + add_view : 봇이 켜질 때 영구 View를 다시 등록해요. 이 두 줄 덕분에 봇을 재시작해도 예전에 띄워둔 버튼이 그대로 동작해요.
  • @commands.has_permissions(administrator=True) : 관리자만 !역할판 명령어를 쓸 수 있게 막아줘요.

📌 3단계 - 입장하면 개인 음성 채널 자동 생성

마지막은 요즘 서버들이 다 쓰는 Join to Create예요. "➕ 채널 만들기" 같은 음성 채널을 하나 만들어두고, 누가 거기 들어오면 그 사람만의 방을 즉석에서 만들어 옮겨주는 기능이에요. 방이 비면 자동으로 사라져서 서버도 깔끔하게 유지돼요.

음성 채널 이동은 on_voice_state_update 이벤트로 감지해요.

Python
CREATOR_CHANNEL_ID = 123456789012345678   # '➕ 채널 만들기' 음성 채널 ID
temp_channels = set()                     # 우리가 만든 임시 채널 ID 보관

@bot.event
async def on_voice_state_update(member, before, after):
    # (생성) '만들기' 채널에 들어오면 개인 채널을 만들고 이동
    if after.channel is not None and after.channel.id == CREATOR_CHANNEL_ID:
        new_channel = await member.guild.create_voice_channel(
            name=f"🔊 {member.display_name}의 방",
            category=after.channel.category,
        )
        temp_channels.add(new_channel.id)
        await member.move_to(new_channel)

    # (삭제) 우리가 만든 임시 채널이 비면 제거
    if before.channel is not None and before.channel.id in temp_channels:
        if len(before.channel.members) == 0:
            await before.channel.delete()
            temp_channels.discard(before.channel.id)

하나씩 뜯어볼게요 💡

  • on_voice_state_update(member, before, after) : 누가 음성 채널을 옮길 때마다 실행돼요. before는 떠난 채널, after는 들어간 채널이에요.
  • after.channel.id == CREATOR_CHANNEL_ID : 방금 들어간 곳이 "만들기" 채널인지 확인해요. 맞으면 새 방을 만들 차례!
  • create_voice_channel(name, category) : 개인 음성 채널을 만들어요. category를 원본과 같게 주면 같은 카테고리 아래에 깔끔하게 생겨요.
  • member.move_to(new_channel) : 방금 만든 방으로 멤버를 슝 옮겨줘요.
  • len(before.channel.members) == 0 : 떠난 방에 아무도 안 남았으면 delete()로 지워요. temp_channels로 "우리가 만든 방"만 지우게 안전장치를 뒀어요.

💡 CREATOR_CHANNEL_ID는 디스코드에서 개발자 모드를 켠 뒤(설정 → 고급 → 개발자 모드), 만들기용 음성 채널을 우클릭 → "ID 복사"로 얻을 수 있어요.

📌 전체 코드 (복붙 OK)

아래 코드를 복사해서 TOKEN, AUTO_ROLE_NAME, CREATOR_CHANNEL_ID, BUTTON_ROLES만 내 서버에 맞게 바꾸면 바로 돌아가요.

Python
# discord.py 2.x (2.7.1 기준) / Python 3.9+
import discord
from discord.ext import commands

# ── 설정 ──────────────────────────────
TOKEN = "여기에_봇_토큰"
AUTO_ROLE_NAME = "새싹"                       # 입장 시 자동으로 줄 역할 이름
CREATOR_CHANNEL_ID = 123456789012345678       # '➕ 채널 만들기' 음성 채널 ID

# 버튼 역할 목록 (custom_id, 역할이름, 버튼라벨, 이모지)
BUTTON_ROLES = [
    ("role_notice", "공지알림", "공지 알림", "🔔"),
    ("role_game",   "게임메이트", "게임 메이트", "🎮"),
    ("role_study",  "스터디원",  "스터디원",   "📚"),
]

intents = discord.Intents.default()
intents.members = True          # 멤버 입장 감지 (프리빌리지드 인텐트)
intents.message_content = True  # 접두사 명령어용
# voice_states 는 default() 에 이미 포함돼 있어요.

# ── 버튼 역할 View ────────────────────
class RoleButton(discord.ui.Button):
    def __init__(self, custom_id, role_name, label, emoji):
        super().__init__(
            label=label, emoji=emoji,
            style=discord.ButtonStyle.secondary,
            custom_id=custom_id,        # 영구 View 는 custom_id 필수!
        )
        self.role_name = role_name

    async def callback(self, interaction: discord.Interaction):
        role = discord.utils.get(interaction.guild.roles, name=self.role_name)
        if role is None:
            await interaction.response.send_message(
                f"❌ '{self.role_name}' 역할이 없어요. 먼저 서버에 만들어 주세요!",
                ephemeral=True)
            return
        if role in interaction.user.roles:
            await interaction.user.remove_roles(role)
            msg = f"'{role.name}' 역할을 뺐어요. 👋"
        else:
            await interaction.user.add_roles(role)
            msg = f"'{role.name}' 역할을 드렸어요! 🎉"
        await interaction.response.send_message(msg, ephemeral=True)

class RoleView(discord.ui.View):
    def __init__(self):
        super().__init__(timeout=None)   # 영구 View 는 timeout=None!
        for custom_id, role_name, label, emoji in BUTTON_ROLES:
            self.add_item(RoleButton(custom_id, role_name, label, emoji))

# ── 봇 본체 ───────────────────────────
class MyBot(commands.Bot):
    def __init__(self):
        super().__init__(command_prefix="!", intents=intents)

    async def setup_hook(self):
        # 재시작 후에도 버튼이 동작하도록 영구 View 재등록
        self.add_view(RoleView())

bot = MyBot()
temp_channels = set()   # 우리가 만든 임시 음성 채널 ID 보관

@bot.event
async def on_ready():
    print(f"✅ {bot.user} 로 로그인했어요!")

# 1) 입장하면 자동으로 역할 부여
@bot.event
async def on_member_join(member: discord.Member):
    role = discord.utils.get(member.guild.roles, name=AUTO_ROLE_NAME)
    if role is not None:
        await member.add_roles(role)
        print(f"👋 {member.name} → '{role.name}' 역할 부여 완료")

# 2) 역할 버튼 패널 띄우기 (관리자만)
@bot.command(name="역할판")
@commands.has_permissions(administrator=True)
async def role_panel(ctx: commands.Context):
    embed = discord.Embed(
        title="🎭 역할 받기",
        description="버튼을 누르면 역할을 드려요! 한 번 더 누르면 역할이 빠져요.",
        color=0x5865F2,
    )
    await ctx.send(embed=embed, view=RoleView())

# 3) 지정 채널 입장 시 개인 음성 채널 자동 생성 + 비면 삭제
@bot.event
async def on_voice_state_update(member, before, after):
    # (생성) '만들기' 채널에 들어오면 개인 채널을 만들고 이동
    if after.channel is not None and after.channel.id == CREATOR_CHANNEL_ID:
        new_channel = await member.guild.create_voice_channel(
            name=f"🔊 {member.display_name}의 방",
            category=after.channel.category,
        )
        temp_channels.add(new_channel.id)
        await member.move_to(new_channel)

    # (삭제) 우리가 만든 임시 채널이 비면 제거
    if before.channel is not None and before.channel.id in temp_channels:
        if len(before.channel.members) == 0:
            await before.channel.delete()
            temp_channels.discard(before.channel.id)

bot.run(TOKEN)

📌 실행 결과

봇을 켜고 서버에서 하나씩 확인해봐요.

  1. 새 계정으로 입장 → 자동으로 "새싹" 역할이 딱 붙어요. 🌱
  2. 관리자가 !역할판 입력 → 버튼 3개가 달린 임베드가 떠요. 버튼을 누르면 "역할을 드렸어요! 🎉", 다시 누르면 "역할을 뺐어요 👋" 하고 나만 보이는 메시지로 답해줘요.
  3. "➕ 채널 만들기" 음성 채널 입장 → 곧바로 "🔊 (내이름)의 방"이 생기고 그리로 옮겨져요. 방에서 나오면 방이 스르륵 사라져요.

📌 자주 나는 에러

1. discord.errors.Forbidden: 403 Missing Permissions 원인: 봇에게 권한이 없거나, 부여하려는 역할이 봇 역할보다 위에 있어요. 해결: 서버 설정 → 역할에서 봇 역할을 위로 올리고, 봇에 역할 관리 / 채널 관리 / 멤버 이동 권한을 켜주세요. 디스코드는 "자기보다 높은 역할"은 절대 못 건드리게 막아둡니다.

2. on_member_join이 아예 실행 안 됨 원인: Server Members Intent가 꺼져 있어요. 해결: 개발자 포털 → Bot → Privileged Gateway Intents에서 Server Members Intent를 켜고, 코드에도 intents.members = True를 넣어요. 둘 다 켜야 동작해요.

3. 봇을 재시작하니 버튼이 "상호작용 실패" 원인: 영구 View 조건을 안 지켰어요. 해결: View는 timeout=None, 모든 버튼은 custom_id를 갖고, setup_hook에서 add_view로 다시 등록했는지 확인하세요. 세 개가 세트예요.

4. 임시 음성 채널이 안 지워짐 원인: 봇에 채널 관리 권한이 없거나, temp_channels에 ID가 안 담겼어요. 해결: 채널 관리 권한을 확인하고, 채널을 만들 때 temp_channels.add(...)로 ID를 저장했는지 점검하세요.

📌 마무리

오늘은 서버 관리를 통째로 봇에게 넘기는 세 가지 자동화를 만들어봤어요. 입장 시 자동 역할, 버튼 역할 셀프 지급, 음성 채널 자동 생성까지 — 이 세 개만 있어도 관리자의 반복 업무가 확 줄어요. 특히 영구 View(timeout=None + custom_id + add_view)는 앞으로 어떤 버튼/메뉴를 만들든 계속 쓰이니 꼭 익혀두세요! 💪

다음 #14편에서는 JSON과 SQLite로 봇 데이터를 저장하는 법을 다뤄볼게요. 역할·설정·기록을 껐다 켜도 잃지 않게 만드는 거죠. 기대해주세요! 🙌

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

연재 · 32편 중 13번째

디스코드 봇 만들기

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