[시행착오편] 디스코드 봇 만들기: 포스트 자동 생성 봇

2025. 9. 1. 20:37·TIL

첫 번째 시행착오: Heroku 배포 실패

문제 상황

로컬에서는 완벽하게 작동하던 봇이 Heroku에 배포하자 다음과 같은 오류가 발생했습니다:

Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds
State changed from starting to crashed

문제 분석 과정

이 오류 메시지를 분석해보면:

  • Boot timeout: 애플리케이션이 지정된 시간 내에 시작되지 않음
  • failed to bind to $PORT: HTTP 포트에 바인딩하지 못함
  • Web process: Heroku가 이 앱을 웹 애플리케이션으로 인식함

근본 원인 이해

Heroku는 기본적으로 모든 애플리케이션을 웹 애플리케이션으로 간주합니다. 웹 애플리케이션은 HTTP 요청을 받기 위해 특정 포트에서 서버를 실행해야 하는데, 디스코드 봇은 HTTP 서버가 아니라 WebSocket 클라이언트이므로 포트 바인딩이 불필요합니다.

해결 과정

1. Procfile 생성
프로젝트 루트에 Procfile 파일 생성:

worker: node index.js

이는 Heroku에게 "이 앱은 웹 서버가 아니라 백그라운드 워커입니다"라고 알려주는 역할입니다.

2. Dyno 타입 변경

heroku ps:scale web=0 worker=1

이 명령어는 웹 dyno를 0개로, 워커 dyno를 1개로 설정합니다.

학습 포인트

  • 클라우드 플랫폼은 애플리케이션 타입에 따라 다른 실행 방식을 가짐
  • Procfile은 Heroku에게 앱을 어떻게 실행할지 알려주는 설정 파일
  • 웹 애플리케이션과 백그라운드 서비스의 차이점 이해

두 번째 시행착오: 주차 계산 오류

문제 발견

!test 명령어를 실행했을 때 "테스트 포스트 생성 완료" 메시지는 나타나지만 실제 포스트는 생성되지 않았습니다.

디버깅 전략

문제를 찾기 위해 createWeeklyPost 함수에 단계별 로깅을 추가했습니다:

async function createWeeklyPost() {
    try {
        console.log('🚀 createWeeklyPost() 시작');

        const channel = client.channels.cache.get(process.env.FORUM_CHANNEL_ID);
        console.log('📡 Channel 찾기:', channel ? '성공' : '실패');
        console.log('📋 Channel 타입:', channel?.type, 'vs', ChannelType.GuildForum);

        const weekInfo = getWeekInfo();
        console.log('📅 Week Info:', weekInfo);

        const imageIndex = (weekInfo.week - 7) % weeklyImages.length;
        const imageName = weeklyImages[imageIndex];
        console.log('🖼️ Image Info:', { imageIndex, imageName });

        // ... 나머지 코드
    } catch (error) {
        console.error('❌ Failed to create weekly post:', error);
        console.error('Error stack:', error.stack);
    }
}

문제 발견

로그 결과:

📅 Week Info: { week: 6, period: '08.25 ~ 08.31' }
🖼️ Image Info: { imageIndex: -1, imageName: undefined }

분석: 8월 31일 기준으로 계산했을 때 6주차가 나왔고, 이는 7주차부터 시작하는 이미지 배열에서 인덱스 -1을 만들어 undefined를 반환했습니다.

해결책 구현

function getWeekInfo(date = new Date()) {
    const week7StartDate = new Date(2025, 8, 1);
    const diffTime = date.getTime() - week7StartDate.getTime();
    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));

    // 핵심 수정: 음수 처리
    let weekNumber = diffDays < 0 ? 7 : Math.floor(diffDays / 7) + 7;

    // ... 나머지 로직
}

학습 포인트

  • 엣지 케이스 처리의 중요성: 시작일 이전 날짜에 대한 방어 로직
  • 체계적 디버깅: 단계별 로깅을 통한 문제 지점 정확한 파악
  • 배열 인덱스 계산: 상대적 인덱스 계산 시 음수 처리 필요

세 번째 시행착오: 환경변수와 권한 문제

문제 상황 1: 명령어 미인식

!weekinfo를 입력해도 봇이 반응하지 않았습니다.

진단 과정

client.on('messageCreate', message => {
    // 모든 메시지 수신 여부 확인
    console.log(`메시지 수신: "${message.content}" from ${message.author.id}`);
    console.log(`환경변수 ADMIN_USER_ID: ${process.env.ADMIN_USER_ID}`);
    console.log(`일치 여부: ${message.author.id === process.env.ADMIN_USER_ID}`);

    // ... 명령어 처리
});

발견된 문제들

  1. 인텐트 누락: MessageContent 인텐트가 없어 메시지 내용을 읽을 수 없음
  2. 환경변수 불일치: 로컬 .env와 Heroku Config Vars 간 동기화 문제
  3. 환경 조건: NODE_ENV=production일 때 테스트 명령어 비활성화

해결 과정

1. 인텐트 추가

const client = new Client({
    intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent  // 이 줄 추가!
    ]
});

2. 환경변수 동기화
Heroku Dashboard → Settings → Config Vars에서 모든 환경변수 등록

3. 환경 조건 제거

// 변경 전: 개발환경에서만 작동
if (process.env.NODE_ENV === 'development') {
    client.on('messageCreate', message => { ... });
}

// 변경 후: 모든 환경에서 작동
client.on('messageCreate', message => { ... });

네 번째 시행착오: Discord.js 버전 호환성

경고 메시지 분석

DeprecationWarning: The ready event has been renamed to clientReady

혼란스러웠던 부분

이 경고 메시지 때문에 ready 이벤트가 잘못된 것으로 오해했습니다. 실제로는 최신 Discord.js v14에서 clientReady가 권장되는 이벤트명이었습니다.

올바른 이해

  • ready 이벤트: 여전히 작동하지만 향후 버전에서 제거 예정
  • clientReady 이벤트: 새로운 표준, Gateway READY 이벤트와 구분
// 권장 방식
client.once('clientReady', readyClient => {
    console.log(`${readyClient.user.tag} 봇이 준비되었습니다!`);
});

 


에러 패턴별 해결 전략

1. TokenInvalid 에러

Error [TokenInvalid]: An invalid token was provided.

원인: 잘못된 봇 토큰 또는 환경변수 로드 실패
해결:

  1. Discord Developer Portal에서 토큰 재발급
  2. .env 파일 또는 Config Vars 정확성 확인
  3. console.log(process.env.DISCORD_TOKEN)으로 로드 여부 확인

2. Missing Permissions 에러

원인: 봇에게 필요한 권한이 부족
해결:

  1. Discord 서버에서 봇 역할 권한 확인
  2. 채널별 권한 설정 점검
  3. OAuth2 URL 재생성하여 권한 추가

3. Channel Not Found 에러

원인: 잘못된 채널 ID 또는 봇이 해당 채널에 접근 권한 없음
해결:

  1. 개발자 모드에서 채널 ID 재확인
  2. 봇이 해당 서버와 채널에 참여되어 있는지 확인
  3. 채널 타입이 예상과 일치하는지 검증

개발 과정에서 배운 핵심 개념들

1. 비동기 프로그래밍 패턴

Promise와 async/await 이해:
디스코드 API 호출은 모두 비동기입니다. 순차적 실행이 필요한 작업(스레드 생성 → 리액션 추가)에서는 적절한 await 사용이 중요합니다.

// 잘못된 방식: 리액션이 동시에 실행되어 Rate Limit 발생 가능
reactions.forEach(emoji => {
    thread.lastMessage?.react(emoji);
});

// 올바른 방식: 순차 실행으로 안정성 확보
for (const emoji of reactions) {
    await thread.lastMessage?.react(emoji);
    await new Promise(resolve => setTimeout(resolve, 500));
}

2. 에러 핸들링 전략

계층별 에러 처리:

  • 함수 레벨: try-catch로 예외 상황 포착
  • 애플리케이션 레벨: process 이벤트로 전역 에러 처리
  • 사용자 레벨: 명확한 에러 메시지 제공
// 함수 레벨 에러 처리
async function createWeeklyPost() {
    try {
        // ... 주요 로직
    } catch (error) {
        console.error('포스트 생성 실패:', error.message);
        console.error('상세 스택:', error.stack);
    }
}

// 전역 에러 처리
process.on('unhandledRejection', error => {
    console.error('처리되지 않은 Promise 거부:', error);
});

client.on('error', error => {
    console.error('Discord.js 에러:', error);
});

3. 디버깅 방법론

체계적 로깅 전략:

// 상태 추적용 로그
console.log('🚀 함수 시작');
console.log('📡 채널 상태:', channel ? '정상' : '오류');
console.log('📅 계산된 주차:', weekInfo);

// 조건문 분기 추적
if (condition) {
    console.log('✅ 조건 통과');
} else {
    console.log('❌ 조건 실패:', reason);
}

문제 해결 사고 과정의 변화

초기 접근법 (비효율적)

  1. 에러 메시지만 보고 추측
  2. 여러 수정사항을 동시에 적용
  3. 변경 효과를 정확히 파악하지 못함

개선된 접근법 (체계적)

  1. 문제 격리: 하나의 이슈에만 집중
  2. 단계별 검증: 각 수정사항의 효과를 개별적으로 확인
  3. 로그 기반 진단: 추측이 아닌 실제 데이터로 판단
  4. 점진적 해결: 작은 문제부터 해결하여 복잡도 관리

실제 적용 사례

문제: 포스트 생성 실패

1. 함수 진입 여부 확인 → ✅ 진입함
2. 채널 찾기 성공 여부 → ✅ 성공
3. 주차 계산 결과 → ❌ 6주차 (음수 문제)
4. 이미지 인덱스 계산 → ❌ -1 (undefined 발생)
5. 근본 원인: 날짜 계산 로직 오류
저작자표시 비영리 변경금지 (새창열림)
'TIL' 카테고리의 다른 글
  • JavaScript 공식 문서 읽기 1일 차
  • React에서 HTML input 속성을 활용한 실시간 유효성 검증 구현
  • 2025-08-20 Props와 이벤트 객체 'e' 이해하기
  • 2025-08-19 React 공식 문서 - 상호작용 더하기
한비(BIBI)
한비(BIBI)
IT 업계에서 오랫동안 일 하고 싶습니다. 가능하다면 죽을 때까지 배우며 살고 싶습니다. 마케팅과 CX 분야에서 커리어를 쌓았습니다. 지금은 IT 업계에 더 깊이 있게 기여하고자 개발 공부를 하고 있습니다. 이 배움의 여정을 글로 남기고 싶어 블로그를 시작했습니다.
  • 한비(BIBI)
    0과 1로된 세상
    한비(BIBI)
  • 전체
    오늘
    어제
    • 분류 전체보기 (33)
      • 크래프톤 정글 (5)
      • Computer Science (10)
      • 읽고 쓰고 생각하기 (1)
      • 일하면서 배웁니다 (1)
      • TIL (15)
  • 링크

    • LinkedIn
    • Threads
    • Twitter
  • 인기 글

  • 태그

    운영체제구조
    컴퓨터과학입문
    나만무프로젝트
    크래프톤정글
    CPU스케줄링
    시스템설계
    데이터시각화
    gpt인프라
    정글후기
    뉴스피드시스템
  • hELLO· Designed By정상우.v4.10.4
한비(BIBI)
[시행착오편] 디스코드 봇 만들기: 포스트 자동 생성 봇
상단으로

티스토리툴바