디스코드 봇 만들기: 앱과 봇 개념부터 서버 초대까지

디스코드를 쓰다 보면 “이런 거 자동으로 처리해 주는 봇이 있으면 좋겠다” 싶은 순간이 한 번쯤은 있으셨을 텐데요. 출석 체크, 명령어 한 줄로 정보 조회, 새 멤버 환영 인사처럼 반복적인 일을 봇에게 맡기면 커뮤니티 운영이 한결 수월해집니다.
그런데 막상 봇을 만들려고 하면 “App인지 Bot인지”, “토큰은 어디서 받는지”부터 헷갈려서 개발자 포털 화면 앞에서 멈칫하게 되는데요. 그래서 이번 글에서는 꼭 필요한 개념부터 차근차근 잡고, 개발자 포털에서 앱을 만들어 봇 토큰을 발급받아 내 서버에 초대한 뒤, discord.js로 봇을 온라인으로 띄우는 첫 실행까지 해보겠습니다. 봇이 실제로 슬래시 커맨드에 반응하게 만드는 건 그다음 이야기고요.
Discord 앱과 봇은 무엇이 다를까
봇을 만들기 전에 헷갈리기 쉬운 용어부터 정리하고 갈게요. Discord에서 우리가 실제로 만드는 건 애플리케이션(Application)입니다. 개발자 포털에 등록되는 이 애플리케이션이 토큰, 권한, 설정 같은 모든 것을 담는 그릇이에요.
그리고 봇(Bot)은 그 애플리케이션에 추가하는 구성 요소 중 하나입니다. 애플리케이션에 봇을 붙이면 인증에 쓰는 토큰이 발급되고, 서버에서는 이름 옆에 앱 태그(영문 클라이언트에서는 APP)가 붙은 특별한 멤버로 등장해요. 즉 “애플리케이션이 본체, 봇은 그 본체가 서버 안에서 활동하기 위한 분신”이라고 생각하면 편합니다.
이렇게 만든 앱은 슬래시 커맨드나 버튼 같은 인터랙션에 반응하고, 서버에서 일어나는 이벤트에 실시간으로 대응하며, 구독이나 결제 같은 수익화까지 할 수 있어요.
개발자 포털이라는 관제탑
봇 개발의 모든 출발점은 Discord Developer Portal입니다. 코드를 한 줄도 짜기 전에, 이 웹 콘솔에서 앱을 등록하고 토큰과 권한을 발급받아야 해요. 비유하자면 포털은 관제탑이고, 우리가 나중에 작성할 코드는 그 관제탑이 내준 토큰과 키를 들고 실제로 움직이는 비행기인 셈입니다.
포털에 로그인하면 내가 만든 애플리케이션 목록이 보이고, 앱 하나를 열면 왼쪽에 여러 탭이 늘어서 있어요. (포털은 브라우저 언어를 따라 한국어로도 보이니, 아래에서는 영문 라벨 뒤에 한글 표기를 괄호로 함께 적어둘게요.) 처음엔 어디를 봐야 할지 막막하니, 앞으로 자주 드나들 탭만 먼저 지도로 그려두겠습니다.
General Information(일반 정보): 앱의 신분증입니다. 커맨드를 어느 앱에 등록할지 식별하는 Application ID(애플리케이션 ID), 그리고 HTTP 인터랙션의 서명을 검증할 때 쓰는 Public Key(공개 키)가 여기 있어요.Bot(봇): 앱에 봇을 붙이고 봇 토큰을 발급하는 탭입니다. 봇의 특권 인텐트를 켜는 토글도 여기 있고요.OAuth2: “Discord로 로그인” 같은 사용자 인증에 쓰는 Client Secret과 리다이렉트 설정이 모인 곳입니다.Installation(설치): 봇을 서버에 초대하는 링크와, 그 봇이 가질 스코프와 권한을 고르는 탭이에요.
지금 이 탭들을 전부 이해할 필요는 없습니다. 이번 글에서 실제로 쓸 곳은 General Information, Bot, Installation 세 군데뿐이고, 나머지는 필요해질 때 다시 찾아오면 돼요.
앱은 디스코드와 어떻게 주고받을까
봇 개발이 막막하게 느껴지는 이유는 보통 “내 코드가 디스코드와 어떻게 연결되는지”가 안 그려져서인데요. 크게 나가는 방향과 들어오는 방향 두 갈래로 나눠 보면 그림이 잡힙니다.
나가는 방향은 REST API 하나뿐입니다. 내가 디스코드에 메시지를 보내거나 커맨드를 등록하는 것처럼, 뭔가를 시킬 때는 전부 평범한 HTTP 요청을 보내요. 봇이든 웹 서비스든 디스코드에 무언가를 요청할 때 공통으로 쓰는 창구입니다. Discord REST API 직접 호출하기에서 라이브러리 없이 맨손으로 다뤄봅니다.
들어오는 방향은 두 가지 중 하나를 고릅니다. 디스코드에서 일어난 일을 내 앱이 전달받는 방법인데, 이 둘은 상호 배타적이에요.
- 게이트웨이(Gateway): 디스코드와 WebSocket 연결을 항상 열어두고, 서버의 메시지나 멤버 입장, 리액션 같은 이벤트를 실시간으로 받습니다. 받을 수 있는 이벤트가 가장 넓지만, 연결을 늘 유지해야 해서 봇을 24시간 켜둘 곳이 필요해요. discord.js가 기본으로 쓰는 방식이고, WebSocket 수명주기나 하트비트 같은 안쪽 이야기는 게이트웨이로 실시간 이벤트 받기에서 깊이 다룹니다.
- HTTP 인터랙션: 슬래시 커맨드나 버튼처럼 사용자가 앱을 부를 때만, 디스코드가 내가 등록한 URL로 요청을 POST해 줍니다. 평소엔 떠 있을 필요가 없어 서버리스에 잘 맞지만, 받을 수 있는 게 인터랙션으로 한정돼요. 이 방식으로 봇을 만드는 과정은 서버리스 봇 만들기에서 다룹니다.
정리하면 REST로 시키고, 게이트웨이나 HTTP 인터랙션으로 전달받는다는 그림입니다. 들어오는 방향의 두 방법 중 게이트웨이는 늘 연결을 유지해야 하는 만큼 다뤄야 할 것도 많고, HTTP 인터랙션은 상대적으로 단순한 대신 인터랙션만 받을 수 있어요. 이 트레이드오프는 각 방법을 실제로 만져볼 때 다시 짚어보겠습니다.
앱 만들고 봇 토큰 받기
이제 손을 움직여 볼게요. 앞서 소개한 Discord Developer Portal에 접속해서 New Application(신규 애플리케이션) 버튼으로 새 애플리케이션을 하나 만듭니다. 이름은 나중에 바꿀 수 있으니 편하게 지으면 돼요.
앱이 만들어지면 왼쪽 메뉴의 Bot(봇) 탭으로 이동합니다. Reset Token(토큰 초기화) 버튼을 누르면 봇 토큰이 발급되는데요. 이 토큰은 봇의 비밀번호와 같아서 화면을 벗어나면 다시 볼 수 없으니 안전한 곳에 복사해 두세요. 혹시 유출됐다면 같은 버튼으로 즉시 재발급하면 기존 토큰은 무효가 됩니다.
이번에 눈여겨볼 값은 두 가지예요. 방금 발급한 봇 토큰, 그리고 General Information(일반 정보) 탭에 있는 Application ID(애플리케이션 ID)입니다. Application ID는 나중에 커맨드를 어느 앱에 등록할지 식별하는 데 쓰입니다. (같은 화면의 Public Key(공개 키)는 HTTP 인터랙션을 받을 때 필요한 값인데, 지금 당장은 쓰지 않으니 슬래시 커맨드와 인터랙션 다루기에서 다시 만나요.)
서버에 봇 초대하기
봇을 만들었으니 내 서버에 들여보낼 차례입니다. 왼쪽 메뉴의 Installation(설치) 탭으로 가면 봇을 설치하는 링크와 설정이 모여 있어요.
Default Install Settings(기본 설치 설정)에서 봇이 가질 스코프와 권한을 고릅니다. 서버에 들어가는 봇이라면 Guild Install(길드 설치) 컨텍스트에 bot 스코프와 applications.commands 스코프를 추가하고, 아래 권한 목록에서는 일단 Send Messages(메시지 보내기) 정도만 골라 줍니다. 권한은 최소한으로 시작하고 필요할 때 늘리는 게 안전해요. (권한 모델은 Discord 권한과 역할 이해하기에서 자세히 다룹니다.)
스코프와 권한을 정했다면, 같은 화면의 Install Link(설치 링크) 섹션에서 Discord 제공 링크를 복사합니다. 이 링크는 설정을 따로 저장하지 않아도 처음부터 제공되니, 그대로 브라우저에서 열고 초대할 서버를 선택하면 봇이 들어와요. 다만 아직은 멤버 목록에 오프라인으로 보일 텐데, 이제 이 봇을 코드로 깨워볼 차례예요.
discord.js로 봇 깨우기
이제 이 봇을 코드로 깨워볼 차례입니다. 봇을 다루는 라이브러리는 여러 개지만, 이 글에서는 가장 널리 쓰이는 discord.js를, 자바스크립트 런타임으로는 Bun을 씁니다. 폴더를 만들고 설치부터 할게요.
mkdir my-discord-bot && cd my-discord-bot
bun init -y
bun add discord.js
npm이 익숙하다면 npm install discord.js로도 됩니다. discord.js는 메이저 버전마다 API가 꽤 달라지니, 이 글 기준인 14 버전인지 확인해 주세요.
봇 토큰 같은 비밀 값은 코드에 직접 박지 않고 .env 파일로 뺍니다. Bun은 .env를 자동으로 읽어 process.env에 넣어주니 별도 라이브러리도 필요 없어요. 이 파일은 절대 깃에 올리면 안 되니 .gitignore에 .env를 꼭 추가해 두세요. 지금 필요한 건 방금 발급받은 봇 토큰 하나뿐입니다.
DISCORD_TOKEN=여기에_봇_토큰
이제 봇을 깨우는 최소 골격을 index.js에 작성합니다.
import { Client, Events, GatewayIntentBits } from "discord.js";
// 어떤 이벤트를 받을지(intents)를 지정해 클라이언트를 만듭니다
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// 봇이 준비되면 딱 한 번 실행됩니다
client.once(Events.ClientReady, (c) => {
console.log(`로그인 완료! ${c.user.tag}`);
});
// 토큰으로 디스코드에 로그인합니다
client.login(process.env.DISCORD_TOKEN);
bun run index.js로 실행하면 콘솔에 “로그인 완료!”가 찍히고, 서버 멤버 목록에서 봇이 온라인으로 바뀝니다. 첫 봇이 살아 움직이는 순간이에요. 🎉
여기서 신기한 점이 하나 있어요. 우리는 공개 URL을 열지도, 포트를 뚫지도 않았는데 봇이 온라인이 됐습니다. 비결은 client.login()이 내 컴퓨터에서 디스코드로 나가는(outbound) WebSocket 연결을 열기 때문이에요. 내가 먼저 전화를 걸어 통화를 계속 열어두는 셈이라, 디스코드는 그 열린 회선으로 이벤트를 밀어 넣습니다. 덕분에 노트북에서 바로 띄울 수 있는 대신, 그 프로세스가 꺼지면 봇도 오프라인이 돼요. 이 연결의 안쪽(WebSocket 수명주기, 하트비트, 재연결)은 게이트웨이로 실시간 이벤트 받기에서 깊이 다룹니다.
방금 넘긴 intents는 봇이 디스코드로부터 어떤 종류의 이벤트를 받을지 미리 신청하는 구독 목록이에요. 지금은 서버 정보만 있으면 되니 Guilds 하나로 충분합니다. 그리고 준비 완료 이벤트를 등록할 때 client.once("ready", ...)처럼 문자열을 직접 쓰는 예제를 많이 보셨을 텐데요. discord.js 14의 최신 버전에서는 이 이벤트의 내부 이름이 ready에서 clientReady로 바뀌었습니다. 문자열을 직접 박아두면 이런 변화에 깨질 수 있으니, 위 코드처럼 Events.ClientReady 같은 열거형(enum) 상수를 쓰는 습관을 들이는 게 안전해요.
마치며
앱과 봇의 관계를 잡고, 개발자 포털에서 토큰을 발급해 서버에 초대하고, discord.js로 봇을 온라인으로 띄우는 첫 실행까지 한 바퀴를 돌았습니다. 봇이 디스코드와 주고받는 방향(나가는 REST, 들어오는 게이트웨이와 HTTP 인터랙션)이라는 큰 그림도 함께 잡았고요.
지금 띄운 건 게이트웨이 봇이에요. 항상 연결을 유지하는 대신, 메시지나 멤버 입장 같은 서버의 모든 이벤트를 받을 수 있죠. 여기서부터 시리즈는 두 갈래로 나뉩니다. 만들려는 봇에 맞는 쪽을 따라가면 돼요.
- 게이트웨이 길: 방금 띄운 이 봇을 깊이 파고드는 쪽입니다. 서버에서 벌어지는 온갖 이벤트에 반응하는 봇이라면 이쪽이에요. WebSocket의 안쪽부터 봇을 24시간 운영하는 배포까지 게이트웨이로 실시간 이벤트 받기에서 이어갑니다.
- 서버리스 길: 슬래시 커맨드나 버튼에만 반응하면 되고 봇을 항상 켜두기 싫다면 이쪽입니다. 게이트웨이 없이 HTTP 인터랙션으로 가는데, Discord REST API 직접 호출로 기초를 다지고 서버리스 봇 만들기를 거쳐 인터랙션과 컴포넌트, 권한으로 이어져요.
봇을 직접 코드로 짜는 대신 기성 봇을 AI에게 맡기는 방법이 궁금하다면 Discord MCP 서버 활용법도 함께 보세요.
더 자세한 내용은 Discord 개발자 문서를 참고하세요.
This work is licensed under CC BY 4.0