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

디스코드 봇 만들기#17RAG로 문서 기반 답변봇 만들기! 우리 서버 전용 FAQ 봇 (파이썬 예제)

목차15개 항목

안녕하세요! Mojangcam입니다 😎

지난 #16편에서는 봇이 정해진 시간에 먼저 말을 거는 리마인더·알림 기능을 만들어봤어요.

오늘은 한 단계 더 나아가서, 우리 서버 문서를 읽고 대답하는 봇을 만들어볼게요. 이런 걸 RAG(검색 증강 생성, Retrieval-Augmented Generation) 라고 불러요!

서버를 운영하다 보면 이런 질문이 매일 반복되죠.

  • "서버 규칙 어디서 봐요?"
  • "모드 설치 방법 좀요"
  • "신청 양식 어떻게 써요?"

공지에 다 적어놨는데도 물어봐요 😂 이걸 봇이 대신 답해주면 어떨까요? 그것도 아무 말이나 지어내지 않고, 우리가 준 문서만 보고 말이죠. 오늘 그 봇을 만들어봅니다!

🧠 RAG가 뭔가요? (그냥 AI 붙이면 안 되나요?)

#9편에서 Claude·ChatGPT API를 봇에 붙여봤어요. 그런데 그 봇한테 "우리 서버 규칙 알려줘"라고 물으면 어떻게 될까요? 모릅니다. 우리 서버 공지는 AI가 배운 적이 없으니까요. 심하면 그럴듯하게 지어냅니다(환각, Hallucination).

RAG는 이 문제를 아주 단순한 방법으로 풀어요.

  1. 미리 우리 문서를 잘게 쪼개서 저장해둡니다
  2. 질문이 오면 그 질문과 가장 비슷한 조각 몇 개만 찾습니다
  3. 그 조각을 AI에게 같이 넘기면서 "이것만 보고 답해"라고 시킵니다

즉 AI를 다시 학습시키는 게 아니라, 답할 때 커닝페이퍼를 쥐여주는 것이에요. 그래서 좋은 점이 많아요.

장점 설명
파인튜닝 불필요 모델을 다시 학습시킬 필요 없이, 문서 파일만 바꾸면 끝이에요
출처 표시 가능 어떤 문서를 보고 답했는지 같이 보여줄 수 있어요
비용 절약 문서 전체가 아니라 관련 조각 3개만 보내니 토큰이 확 줄어요
거짓말 감소 "문서에 없으면 없다고 해"라고 규칙을 줄 수 있어요

📌 준비물

  • Python 3.9 이상 (Anthropic SDK가 3.9+ 를 요구해요)
  • discord.py 2.x — 슬래시 명령어용
  • openai 2.x — 임베딩(검색)용
  • anthropic 0.x — 답변 생성용
  • numpy 2.x — 유사도 계산용
mipsasm
pip install discord.py openai anthropic numpy

필요한 키는 3개예요.

키 어디서 받나요?
디스코드 봇 토큰 Discord Developer Portal에서 발급 (#1편 참고)
OpenAI API 키 platform.openai.com — 임베딩용
Anthropic API 키 Claude 콘솔 — 답변 생성용

폴더는 이렇게 준비해주세요.

stylus
my-rag-bot/
├── docs/              ← 여기에 문서(.txt / .md)를 넣어요
│   ├── 서버규칙.txt
│   └── 모드설치가이드.md
├── build_index.py     ← 색인 만드는 스크립트 (한 번만 실행)
└── rag_bot.py         ← 실제 봇

💡 키는 코드에 직접 적지 말고 환경변수로 넣어주세요. 윈도우는 set OPENAI_API_KEY=sk-..., 맥·리눅스는 export OPENAI_API_KEY=sk-... 예요.

📌 1단계 - 문서를 조각내기 (청킹)

문서를 통째로 AI에게 주면 토큰이 폭발해요. 그래서 500글자씩 잘라서 씁니다.

SQL
CHUNK_SIZE = 500      # 조각 하나의 글자 수
CHUNK_OVERLAP = 100   # 조각끼리 겹칠 글자 수

def split_text(text, size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
    chunks, start = [], 0
    while start < len(text):
        piece = text[start:start + size].strip()
        if piece:
            chunks.append(piece)
        start += size - overlap
    return chunks

하나씩 뜯어볼게요 💡

코드 설명
CHUNK_SIZE = 500 한 조각의 크기예요. 너무 작으면 문맥이 끊기고, 너무 크면 엉뚱한 내용까지 딸려와요
CHUNK_OVERLAP = 100 조각끼리 100글자를 일부러 겹치게 잘라요. 중요한 문장이 조각 경계에서 반토막 나는 걸 막아줍니다
start += size - overlap 그래서 400글자씩만 전진해요. ⚠️ size가 overlap보다 커야 무한 루프가 안 나요!

1,200글자 문서를 넣으면 500 / 500 / 400 이렇게 3조각이 나와요.

📌 2단계 - 조각을 임베딩으로 바꿔 저장하기

임베딩(Embedding) 은 글을 숫자 목록(벡터) 으로 바꾼 거예요. 의미가 비슷한 글끼리는 숫자도 비슷하게 나옵니다. 그래서 "규칙"과 "수칙"처럼 글자는 달라도 뜻이 비슷하면 검색이 돼요. 단순 키워드 검색과 결정적으로 다른 점이죠!

코드
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
EMBED_MODEL = "text-embedding-3-small"

def embed_all(texts):
    vectors = []
    for i in range(0, len(texts), 100):     # 100개씩 끊어서 요청
        batch = texts[i:i + 100]
        res = client.embeddings.create(model=EMBED_MODEL, input=batch)
        vectors.extend([d.embedding for d in res.data])
        print(f"  임베딩 {i + len(batch)}/{len(texts)} 완료")
    return vectors
항목 설명
text-embedding-3-small OpenAI의 가벼운 임베딩 모델이에요. 벡터 길이는 1536차원
input=batch 리스트로 한 번에 여러 개를 보낼 수 있어요. 100개씩 묶으면 훨씬 빨라요
res.data[i].embedding 결과는 data 리스트로 오고, 순서는 보낸 순서 그대로예요

💰 비용이 궁금하시죠? text-embedding-3-small은 100만 토큰당 약 $0.02 수준이에요. A4 100쪽짜리 문서를 통째로 색인해도 몇 원 수준입니다. (대략치이고 요금은 바뀔 수 있으니 공식 가격표를 확인해주세요!)

만든 벡터는 index.json 파일 하나에 저장해요. #14편에서 다룬 JSON 저장을 그대로 쓰는 거예요 👍

📌 3단계 - 질문과 가장 비슷한 조각 찾기

이제 검색 차례예요. 두 벡터가 얼마나 비슷한지는 코사인 유사도(Cosine Similarity) 로 재요. 값은 -1 ~ 1 사이이고, 1에 가까울수록 비슷하다는 뜻이에요.

nix
import numpy as np

# 미리 전부 길이 1로 맞춰두면(정규화) 나중에 곱셈만 하면 끝!
MATRIX = np.array([it["embedding"] for it in ITEMS], dtype="float32")
MATRIX = MATRIX / np.linalg.norm(MATRIX, axis=1, keepdims=True)

def search(question, top_k=3):
    res = openai_client.embeddings.create(model=EMBED_MODEL, input=[question])
    q = np.array(res.data[0].embedding, dtype="float32")
    q = q / np.linalg.norm(q)

    scores = MATRIX @ q                      # 한 방에 전체 유사도 계산
    top = np.argsort(scores)[::-1][:top_k]   # 점수 높은 순 top_k개
    return [(ITEMS[i], float(scores[i])) for i in top]

하나씩 뜯어볼게요 💡

코드 설명
np.linalg.norm(...) 으로 나누기 벡터 길이를 1로 맞추는 정규화예요. 이러면 코사인 유사도가 그냥 내적(곱셈) 이 돼요
MATRIX @ q @는 행렬 곱이에요. 반복문 없이 모든 조각의 점수를 한 번에 구합니다
np.argsort(scores)[::-1] argsort는 오름차순이라 [::-1]로 뒤집어서 높은 점수부터 가져와요
float(scores[i]) numpy 숫자를 파이썬 float로 바꿔줘야 나중에 포매팅이 편해요

조각이 몇천 개까지는 이 방식으로 충분히 빨라요. 벡터DB 없이도 됩니다!

📌 4단계 - 찾은 조각으로 Claude에게 답변 시키기

찾은 조각 3개를 그대로 붙여서 프롬프트로 넘겨요. 여기서 시스템 프롬프트가 핵심이에요.

Python
from anthropic import Anthropic

claude = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
CHAT_MODEL = "claude-sonnet-5"

SYSTEM_PROMPT = (
    "당신은 디스코드 서버의 문서 도우미입니다. "
    "반드시 아래 '문서' 안의 내용만 근거로 한국어로 답하세요. "
    "문서에 없는 내용이면 지어내지 말고 '문서에서 찾지 못했어요'라고 답하세요. "
    "답변은 3~5문장으로 짧게 정리하세요."
)

def make_answer(question, hits):
    context = "\n\n".join(
        f"[{i + 1}] ({it['source']})\n{it['text']}"
        for i, (it, _) in enumerate(hits)
    )
    msg = claude.messages.create(
        model=CHAT_MODEL,
        max_tokens=600,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": f"문서:\n{context}\n\n질문: {question}"}],
    )
    return msg.content[0].text
항목 설명
"문서 안의 내용만 근거로" 이 한 줄이 거짓말을 막는 핵심이에요. 빼면 봇이 상상해서 답합니다 😅
'문서에서 찾지 못했어요' 모를 때 어떻게 답할지 탈출구를 미리 지정해주는 거예요
system= messages에 섞지 말고 system 파라미터로 따로 넘기는 게 정석이에요
msg.content[0].text Claude 응답은 블록 리스트라 첫 블록의 .text를 꺼내요

📌 5단계 - 디스코드 슬래시 명령어로 연결하기

마지막! 여기서 놓치면 100% 터지는 함정 2개가 있어요.

less
@bot.tree.command(name="질문", description="서버 문서에서 찾아 답변해드려요")
@discord.app_commands.describe(내용="궁금한 내용을 적어주세요")
async def ask(interaction: discord.Interaction, 내용: str):
    await interaction.response.defer(thinking=True)   # ① 3초 제한 회피
    hits = await asyncio.to_thread(search, 내용)       # ② 블로킹 → 스레드로
    answer = await asyncio.to_thread(make_answer, 내용, hits)

    embed = discord.Embed(title="📚 문서 기반 답변", description=answer[:4000],
                          color=0x5865F2)
    await interaction.followup.send(embed=embed)

하나씩 뜯어볼게요 💡

코드 설명
defer(thinking=True) 디스코드는 3초 안에 응답하지 않으면 "애플리케이션이 응답하지 않았습니다" 에러를 띄워요. AI 답변은 5~10초 걸리니 먼저 "생각 중..."으로 시간을 벌어야 해요
followup.send() defer를 했으면 그다음은 response.send_message가 아니라 followup.send 로 보내야 해요
asyncio.to_thread(...) OpenAI·Anthropic 기본 클라이언트는 동기(blocking) 라서 그냥 부르면 봇 전체가 멈춰요. 별도 스레드로 넘겨야 다른 명령어도 정상 동작합니다
answer[:4000] 임베드 설명은 최대 4096자예요. 잘라주는 게 안전해요 (#8편 참고)

📌 전체 코드 (복붙 OK)

1) build_index.py — 색인 만들기 (문서 바뀔 때만 실행)

Python
# 파이썬 3.9+ / openai 2.x / numpy 2.x
# 문서를 조각내서 임베딩으로 바꾼 뒤 index.json 으로 저장합니다.
import os
import json
import glob
from openai import OpenAI

DOCS_DIR = "docs"
INDEX_FILE = "index.json"
EMBED_MODEL = "text-embedding-3-small"
CHUNK_SIZE = 500
CHUNK_OVERLAP = 100

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

def split_text(text, size=CHUNK_SIZE, overlap=CHUNK_OVERLAP):
    """긴 글을 size 글자씩, overlap 만큼 겹치게 잘라줍니다."""
    chunks, start = [], 0
    while start < len(text):
        piece = text[start:start + size].strip()
        if piece:
            chunks.append(piece)
        start += size - overlap
    return chunks

def load_chunks():
    items = []
    paths = sorted(glob.glob(os.path.join(DOCS_DIR, "*.txt")) +
                   glob.glob(os.path.join(DOCS_DIR, "*.md")))
    for path in paths:
        with open(path, encoding="utf-8") as f:
            text = f.read()
        for i, piece in enumerate(split_text(text)):
            items.append({
                "source": os.path.basename(path),
                "chunk_id": i,
                "text": piece,
            })
    return items

def embed_all(texts):
    vectors = []
    for i in range(0, len(texts), 100):
        batch = texts[i:i + 100]
        res = client.embeddings.create(model=EMBED_MODEL, input=batch)
        vectors.extend([d.embedding for d in res.data])
        print(f"  임베딩 {i + len(batch)}/{len(texts)} 완료")
    return vectors

def main():
    items = load_chunks()
    if not items:
        print("❌ docs 폴더에 .txt 또는 .md 파일을 먼저 넣어주세요!")
        return
    print(f"조각 {len(items)}개를 임베딩할게요...")
    vectors = embed_all([it["text"] for it in items])
    for it, vec in zip(items, vectors):
        it["embedding"] = vec
    with open(INDEX_FILE, "w", encoding="utf-8") as f:
        json.dump({"model": EMBED_MODEL, "items": items}, f, ensure_ascii=False)
    print(f"✅ {INDEX_FILE} 저장 완료! (조각 {len(items)}개)")

if __name__ == "__main__":
    main()

2) rag_bot.py — 실제 봇

Python
# discord.py 2.x / 파이썬 3.9+ / openai 2.x / anthropic 0.x / numpy 2.x
import os
import json
import asyncio

import numpy as np
import discord
from discord.ext import commands
from openai import OpenAI
from anthropic import Anthropic

DISCORD_TOKEN = os.environ["DISCORD_TOKEN"]
openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
claude = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

EMBED_MODEL = "text-embedding-3-small"
CHAT_MODEL = "claude-sonnet-5"
TOP_K = 3          # 몇 조각을 참고할지
MIN_SCORE = 0.25   # 이보다 유사도가 낮으면 "못 찾았어요"

# --- 색인 불러오기 ---
with open("index.json", encoding="utf-8") as f:
    index = json.load(f)

ITEMS = index["items"]
MATRIX = np.array([it["embedding"] for it in ITEMS], dtype="float32")
MATRIX = MATRIX / np.linalg.norm(MATRIX, axis=1, keepdims=True)

def search(question, top_k=TOP_K):
    res = openai_client.embeddings.create(model=EMBED_MODEL, input=[question])
    q = np.array(res.data[0].embedding, dtype="float32")
    q = q / np.linalg.norm(q)
    scores = MATRIX @ q
    top = np.argsort(scores)[::-1][:top_k]
    return [(ITEMS[i], float(scores[i])) for i in top]

SYSTEM_PROMPT = (
    "당신은 디스코드 서버의 문서 도우미입니다. "
    "반드시 아래 '문서' 안의 내용만 근거로 한국어로 답하세요. "
    "문서에 없는 내용이면 지어내지 말고 '문서에서 찾지 못했어요'라고 답하세요. "
    "답변은 3~5문장으로 짧게 정리하세요."
)

def make_answer(question, hits):
    context = "\n\n".join(
        f"[{i + 1}] ({it['source']})\n{it['text']}"
        for i, (it, _) in enumerate(hits)
    )
    msg = claude.messages.create(
        model=CHAT_MODEL,
        max_tokens=600,
        system=SYSTEM_PROMPT,
        messages=[{"role": "user", "content": f"문서:\n{context}\n\n질문: {question}"}],
    )
    return msg.content[0].text

intents = discord.Intents.default()

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

    async def setup_hook(self):
        await self.tree.sync()

bot = RagBot()

@bot.event
async def on_ready():
    print(f"✅ 로그인 완료: {bot.user} / 문서 조각 {len(ITEMS)}개")

@bot.tree.command(name="질문", description="서버 문서에서 찾아 답변해드려요")
@discord.app_commands.describe(내용="궁금한 내용을 적어주세요")
async def ask(interaction: discord.Interaction, 내용: str):
    await interaction.response.defer(thinking=True)
    try:
        hits = await asyncio.to_thread(search, 내용)
        if not hits or hits[0][1] < MIN_SCORE:
            await interaction.followup.send("🤔 문서에서 관련 내용을 찾지 못했어요.")
            return
        answer = await asyncio.to_thread(make_answer, 내용, hits)
    except Exception as e:
        await interaction.followup.send(f"⚠️ 답변을 만들지 못했어요: {e}")
        return

    embed = discord.Embed(
        title="📚 문서 기반 답변",
        description=answer[:4000],
        color=0x5865F2,
    )
    embed.add_field(
        name="🔎 참고한 문서",
        value="\n".join(
            f"[{i + 1}] {it['source']} (유사도 {score:.2f})"
            for i, (it, score) in enumerate(hits)
        ),
        inline=False,
    )
    embed.set_footer(text=f"질문: {내용[:80]}")
    await interaction.followup.send(embed=embed)

bot.run(DISCORD_TOKEN)

📌 실행 결과

순서는 딱 두 줄이에요.

vim
python build_index.py    # ① 색인 만들기 (문서 바뀔 때만)
python rag_bot.py        # ② 봇 실행

① 을 실행하면 이렇게 나와요.

코드
조각 12개를 임베딩할게요...
  임베딩 12/12 완료
✅ index.json 저장 완료! (조각 12개)

② 를 실행하고 디스코드에서 /질문 서버 규칙 알려줘 를 입력하면, 봇이 잠깐 "생각 중..."을 띄운 뒤 문서에서 찾은 내용으로 답변 + 참고 문서 목록을 임베드로 보내줘요!

문서에 없는 걸 물어보면? 🤔 문서에서 관련 내용을 찾지 못했어요. 라고 솔직하게 답해요. 이게 RAG의 매력이에요 😎

📌 자주 나는 에러

1. FileNotFoundError: index.json

  • 원인 : build_index.py를 실행하지 않고 바로 봇을 켰어요
  • 해결 : python build_index.py 를 먼저 한 번 돌려주세요

2. KeyError: 'OPENAI_API_KEY'

  • 원인 : 환경변수가 설정되지 않았어요
  • 해결 : 터미널에서 set OPENAI_API_KEY=sk-...(윈도우) / export OPENAI_API_KEY=sk-...(맥·리눅스). ⚠️ 환경변수는 터미널 창마다 따로 잡히니, 봇을 켤 그 터미널에서 설정해야 해요

3. "애플리케이션이 응답하지 않았습니다"

  • 원인 : 3초 안에 응답을 못 했어요
  • 해결 : 명령어 맨 앞에 await interaction.response.defer(thinking=True) 를 꼭 넣으세요

4. 봇이 명령어 하나 쓰는 동안 완전히 멈춰요

  • 원인 : 동기 API 호출이 이벤트 루프를 붙잡고 있어요
  • 해결 : await asyncio.to_thread(함수, 인자) 로 감싸세요. (asyncio.to_thread는 파이썬 3.9 이상에서 써요)

5. 엉뚱한 문서를 물어와요

  • 원인 : 조각이 너무 크거나(문맥 뒤섞임) 너무 작아요(문맥 부족)
  • 해결 : CHUNK_SIZE를 300~800 사이에서 조절하고 색인을 다시 만드세요. TOP_K를 5로 올려보는 것도 방법이에요

6. openai.RateLimitError 또는 insufficient_quota

  • 원인 : API 키에 크레딧이 없거나 분당 요청 한도를 넘었어요
  • 해결 : 결제 정보·잔액을 확인하고, 색인할 때는 배치 크기(100)를 50 정도로 줄여보세요

7. 한글이 \uc11c\ubc84 처럼 깨져서 저장돼요

  • 원인 : json.dump() 기본값이 아스키 변환이에요
  • 해결 : ensure_ascii=False 를 꼭 넣어주세요 (전체 코드엔 이미 들어가 있어요!)

💡 보너스 팁 3가지

① 키를 하나만 쓰고 싶다면? make_answer() 안쪽만 OpenAI 채팅 모델로 바꾸면 돼요. 임베딩·답변 둘 다 OpenAI 키 하나로 끝납니다.

② 문서를 바꿨는데 봇이 옛날 내용을 답해요. 당연해요! index.json은 자동으로 갱신되지 않아요. 문서를 고쳤으면 build_index.py를 다시 돌리고 봇을 재시작해주세요.

③ 조각이 수만 개로 늘어나면? 그때는 JSON 대신 벡터DB(Chroma, FAISS 등)로 옮기는 걸 권해요. 하지만 서버 공지·규칙 정도라면 오늘 방식으로 충분합니다 👍

📌 마무리

오늘은 RAG로 문서 기반 답변봇을 만들어봤어요! 정리하면 이렇습니다 💬

  • 문서를 500자씩 겹치게 조각내고
  • 임베딩으로 바꿔 index.json에 저장하고
  • 질문이 오면 코사인 유사도로 비슷한 조각 3개를 찾아
  • Claude에게 "이것만 보고 답해" 라고 시키는 것

생각보다 어렵지 않죠? 벡터DB도, 파인튜닝도 없이 numpy 곱셈 한 줄로 검색이 끝났어요 😎

다음 편에서는 오늘 만든 봇을 서버 사람들이 실제로 쓸 수 있게 다듬어볼게요. 질문 로그를 남기고, 자주 나온 질문을 통계로 뽑는 기능을 붙여볼 예정이에요. 기대해주세요! 🙌

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

💬 궁금한 점은 댓글로 남겨주세요. 다음 편에서 만나요!

연재 · 32편 중 17번째

디스코드 봇 만들기

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