디스코드 베스트 포스트 추천 봇 만들기 1편 - 프로덕션환경에서 기존 기능 수행하기 (feat. 처음보는 10008에러 해결)

Description: 디스코드 봇을 만들면서 처음에 잘못 설정한 guild install 때문에 봇이 설치안되는 문제, 10008에러를 알아내고 해결하기까지의 트러블 슈팅등을 담고 있습니다.

현재 노트: KR-110.00 a 디스코드 베스트 포스트 추천 봇 만들기 1편 - 프로덕션환경에서 기존 기능 수행하기 (feat. 처음보는 10008에러 해결)
상위 분류: KR-110.00 디스코드 베스트 포스트 추천 봇 제작기

#프로젝트경험 #개인프로젝트 #디스코드봇

디스코드 베스트 포스트 추천 봇 만들기 1편 - 프로덕션환경에서 기존 기능 수행하기 (feat. 처음보는 10008에러 해결)

본 글의 내용은 일반적으로 다음과 같은 흐름으로 진행됩니다.
원하는 것을 구현 시도 -> 문제가 생김 -> 해결
또다른 기능등 시도-> 다시 새로운 문제가 생김 -> 해결
... 구현 또는 수정이나 해결될 때까지 적절하게 반복

디스코드 봇을 만들기 위해서는 디스코드에 대한 이해가 필요하다. 실제로 프로그램의 구현보다는 디스코드의 방식, 구조, 반환 양식등을 이해하는게 구현에서 많이 중요하다고 느꼈습니다. 프로그램 실제 구현시 중요한 것은 도메인지식

모르는 분야에 처음 도전할 때: official docs참조가 아닌 이상 처음에는 정석으로 시도하는 것이 좋은 방향성

디스코드 봇을 처음 사용해보는 환경에서 인터넷과 ai도움으로 어느정도 파악한 후 시도했지만 처으부터 잘모르는 것을 도움없이 실행시킨다는게 쉽지 않았습니다.

$ node main.js 
Logged in as woowa_librarian#2506!
Failed to fetch channel: Missing Access

혹시 디스코드 봇이 들어오지 않아서인가 싶어 일정 시간후 테스트 시도했지만 계속 에러발생.
혹시 몰라 추가 권한 설정 변겅후 OAuth link생성. -> 변화없음
좀 더 검색해본 결과 다른 분들의 경우 디스코드봇이 오프라인에 있다가 실행시 온라인으로 변하는데 제 케이스의 경우 아예 오프라인에도 들어오지 않은 상태였다는것을 관찰을 통해 파악했습니다.
디스코드 봇이 서버의 멤버 리스트에 보이지 않은 상태가 비정상임을 판단.

해결

Bot 탭에서 private bot을위하여 Public Bot을 비활성화함
Pasted image 20241023012841.png

이 설정 비활성화하기 위해서는 Installation에서 Guild Install을 비활성해야함.

근데 이 Guild Install 이 비활성화 되니 OAuth2 private 링크가 활성화가 안됨
여기서 Oauth2 설치 방식도 Integration Type에따라 2가지있는데 User Install은 bot을 허용 안 하는 것 같음.

결론적으로 처음에 보안을 생각해서 무심코 private bot으로 값을 바꿨는데 bot은 INTEGRATION TYPE에서 Guild Install을 해야하고,
그럼 Intallation 에서Guild Install이 켜져 있어야하고, 그럼 Bot 탭에서 Public Bot이 활성화 돼있어야했던 것입니다.

즉 원래라면 기본 Public bot 활성화(디폴트) -> Guild Install -> Oauth2 링크 형태인데
처음 시도하는 제가 임의로 Private bot 활성화 -> Guild Install 비활성화 -> Oauth 2 비활성화 -> (문제확인)
잘 알지 못하는 선택에서 설정을 건드려서 생긴 스노우 볼이었습니다. 그래서 웬만해서 처음하는 경우라면 설정은 디폴트값을 사용할 것...!

포럼채널 활성화하기 디스코드 이해하기

해당 내용은 디스코드에 대한 이해가 필요했던 내용입니다.
실제 환경과 비슷하게 테스트하게 하기 위해 포럼이라는 채널을 만들려고 했는데 선택옵션이 제공되지 않았습니다. 찾아본 결과 다음처럼 기능 활성이 필요했습니다

커뮤니티 활성화 필요 -> 포럼채널

즉 실제 구현하기 전에는 테스트하기 위해 포럼 채널 만들어야지 이지만
실제 구현 하다보면 테스트하기 위해 포럼 채널 만들어야지-> 포럼 채널은 바로 만들 수 없습니다 -> 그럼 어떻게? -> 검색 -> 커뮤니티 활성화 조건 확인 등의 과정이 필요하다는 것을 구현을 직접 해봐야 깨달을 수 있는 점이었습니다.

디스코만의 카테고리 정보가 정해져있어 text-based강제시 실패한 경우

디스코드의 채널 종류와 카테고리가 정해져있었습니다. 처음에는 text-based에 대한 카테고리가 명시가 돼있어서 에러가 발생했습니다. 그래서 해당 정보를 받아서 표기하는 형태로 수정하니 포럼채널의 경우 카테고리 15라는 다른 타입으로 정해져있었습니다.
이번 것도 디스코드에 대한 이해가 필요했는데 한 번 해보면 디스코드엔 여러 채널이 각자의 정보를 가진 형태로 있다라는 사실을 직접 구현해봐야 알 수 있는 정보였습니다...

fetch시 해당 채널 카테고리 예시
Fetched channel: test-forum (ID: 1231234124242), Type: 15

디스코드봇 온라인 하지만 로직의 결과 값이 이상한 경우 쓰레드와 메시지

드디어 디스코드봇이 온라인으로서 동작하는 단계까지 왔습니다. 분명 처음 시작할 때는 일단 봇이 돌아가고 로직을 테스트해야지~ 라고 생각했는데, 일단 봇이 돌아가기 까지 꽤 시간과 노력이 많이 필요했습니다...

메인 포스트(쓰레드)의 이모지는 일치하지만, 그 밑에 달린 것이 스레드가 아니라 메시지라서 집계가 안됨.

$ node main.js 
Logged in as woowa_librarian#2506!
Fetched channel: test-forum (ID: 11111111111111111), Type: 15
Successfully fetched 2 active threads in forum channel.
Processing thread: 두번째 포스트 (ID: 11111111111112)
Fetched 1 messages in thread 두번째 포스트
Total reactions in thread 두번째 포스트: 0
Processing thread: 첫포스트 제목 (ID: 1111111111111113)
Fetched 1 messages in thread 첫포스트 제목
Total reactions in thread 첫포스트 제목: 3
Thread data fetched: [
  {
    threadId: '1298329867963863040',
    threadName: '두번째 포스트',
    totalReactions: 0,
    subThreadCount: 0
  },
  {
    threadId: '1298329750921543731',
    threadName: '첫포스트 제목',
    totalReactions: 3,
    subThreadCount: 0
  }
]

여기서 또 디스코드의 정보 단위에 대한 도메인지식을 요구하게 됩니다. 그냥 사용할 때는 크게 신경을 쓰지 않았지만, 실제로 구현된 정보는 쓰레드라는 단위와 메시지라는 단위가 엄격하게 나뉘어져있었습니다. 그래서 쓰레드가 메인포스트기반이고 그 밑에 달린 일반적인 메시지들이 서브 메시지들이 있었습니다. 이에 대한 이해가 부족한상태로 접근하면 쓰레드의 프로퍼티나 메시지나 접근이 이상해서 문제가 생겨도 해결하기 어려운 상황이었습니다.

추가로 메시지의 경우 엔터를 기준으로 숫자가 나뉘었습니다. 같은 블럭이라도 엔터가 여러번이라면 서브 메시지의 경우 여러번 인식됩니다.

개인 서버에서 디스코드 봇 동작 확인 완료!

이런 저런 절차로 결론적으로는 디스코드 봇이 제 개인서버에서 reaction과 메시지를 수를 수집할 수 있게 됐습니다. 이후에는 프리코스 서버의 관리자분에게 연락하여 서버권한을 획득 후 (디스코드 봇을 해당권한이 없으면 추가 불가) 동작하는게 목표가 됐습니다.

디스코드 봇 프리코스 서버 프라이빗 채널에 추가하기 문제

관리자 권한을 받고 추가하는데 이제 프리코스 서버의 프라이빗 채널에 추가를 시도했지만 처음 겪었던 오프라인에 등록되지 않은 문제가 생겼습니다!

디스코드 봇 링크 검색 후 추가 -> 프리코스서버 개인 채널에 오프라인으로 등록이 안됨
개인 테스트 서버에서 테스트 -> 오프라인 등록 확인 링크 문제 없음
다른 디스코드봇 있는 채널 확인 -> 채널에 봇을 추가하는 것은 문제 없음.

곰곰히 생각해본결과 프리코스 서버에서는 프라이빗채널이고, 개인 테스트 서버는 퍼블릭 채널이라 그런거 같음.

프리코스 서버의 개인 채널에 봇을 멤버로 추가 ->프라이빗채널 멤버리스트에서 봇 확인->해결

동일한 로직인데 개인서버에서는 잘 동작하지만 프로덕션 서버에서는 바로 에러가 발생해 데이터가 저장이 안되는 문제 feat 10008에러

기존 개인 서버에서 2개의 데이터만으로 테스트하여 원하는 데이터를 저장하는 기능을 하는 프로그램을 만들었습니다. 그리고 이를 수백명이 모인 프리코스 서버에서 시도 했는데 바로 에러가 발생하였습니다.

현재 테스트시 흐름
fetch -> 데이터 저장 -> 저장완료 문구 출력
처음시도시 97개 정도의 데이터가 잡히며 한개를 fetch하는데 약 4초정도가 소모되는 상황이었습니다.
그래서 실행후 다른 것을 하다가 왔을때 실패하였고 다음과 같은 상황이었습니다.

데이터를 저장하는 것처럼 터미널에 표시되지만 저장이 빈 배열로 반환됨.
로그인 및 채널에서 정보를 가져오는 것은 괜찮아보임.
그런데 자세히보니 save 했다는 문구가 안보임.
즉 저장이 아예 안 됨

처음으로 든 의심사항은 다음과 같았습니다.

Discord API Limit부터 확인해보기 (10008에러)

https://discord.com/developers/docs/topics/rate-limits/1000

공식홈페이지가 있어서 바로 확인가능하기에 먼저 시도
결론부터 말하면 해당 문제는 아닌것 같음.

일단 횟수가 API 제한 기준이며, 제한을 어길시 속도가 느려진다는것 같음.
특히 초당 50개이상의 요청? 같은 경우

근데내가 호출하는 API자체의 횟수는 fetch정도 밖에 없어서 많아봐야 3개 이하에 여러번 요청하는 게 아님. 그리고 속도가 느려져도 프로그램은 작동해야하는데 이번 데이터 저장이 안되는 경우는 프로그램이 아예 작동을 안함

결론: API제한이 원인은 아닌 것 같음.

fetchdata가 너무 많아서 그런건가 싶음 (10008에러)

개인 테스트 서버에서는 단 2개의 쓰레드만 됐던 것과는 달리, 실제 운영 서버에서는 97개의 쓰레드가 감지됨. 한번에 이 모든 데이터를 가져와서 이후 save 하는 방식이라 혹시 fetch시 데이터가 너무 큰지 테스트.

//  로그인 시작
Successfully fetched 97 active threads in forum channel.

// ... data fetch중

// 프로그램 종료
Thread 테스트? was created on Fri Oct 18 2024, Week 0
스레드 21412444 작성자: abc
Thread metadata: {
  threadId: '1200000000000',
  threadName: '테스트?',
  threadLink: 'https://discord.com/channels/1200000000000/1200000000000',
  creationDate: '2024-10-18T00:57:17.370Z',
  weekNumber: -2,
  mainPostReactions: 3,
  messageCount: 15,
  totalReactions: 20,
  author: 'abc'
}
70번째 쓰레드


Processing thread: 커밋! 자신의 방식을 알려주세요! (ID: 1200000000000)
Failed to fetch threads: Unknown Message
가져온 데이터: []
+++++++++++++++++ start saveCurrentData ++++++++++++++++
------------------- Current thread data saved successfully.----------------
채널 데이터 수집이완료됐습니다.  

일단 테스트 결과 활성 쓰레드가 97개로 나온 것과는 달리 결과는 70번째 쓰레드에서 끝나버림.
충분히 fetchdata가 큰게 원인이라 짐작함. <-결론부터 말하면 틀린 접근
일단 배치사이즈10으로 해서 10개마다 저장하는 식으로 테스트

메시지 받아오는과정에서 문제가생겨서 그런걸 수 있으니, 계속 문제생기느 70번째 쓰레드의 메시지과정에서 터미널에러 출력확인
에러 핸들링하기. 사실 현재 에러발생시 강제로 빈배열 반환하는 로직이였던 상황에서 무시하고 다음 쓰레드 진행하겠끔 return문 제거

Batch로 크기를 줄였는데도 70번째 쓰레드에서계속 에러 (10008에러)

  weekNumber: -2,
  mainPostReactions: 3,
  messageCount: 15,
  totalReactions: 20,
  author: 'abc'
}
70번째 쓰레드


Processing thread: 커밋! 자신의 방식을 알려주세요! (ID: 12900000000000000)
ReferenceError: thread is not defined
    at getMetadata (C:\Users\KMC\Desktop\Project\discord-librarian\getmetadata.js:109:66)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async main (C:\Users\KMC\Desktop\Project\discord-librarian\main.js:16:25)

에러 세부 경로 파악하기 error.stackerror.rawError 사용해보기
의외로 디스코드 에러로 나옴
즉 로직에서의 문제라기보다는 디스코드에서 제대로 fetch가 안 일어난 상황

Failed to fetch threads: Unknown Message
Stack trace: DiscordAPIError[10008]: Unknown Message
...
Raw error details: {
  "message": "Unknown Message",
  "code": 10008
}

트러블슈팅의 키포인트 10008에러 (10008에러)

10008의 경우 정보가 너무 부족함. 다른 에러메시지와는 다르게 다음과 같은 설명만 있기 때문입니다.
https://discord.com/developers/docs/topics/opcodes-and-status-codes

10008 Unknown message
해당 스테이터스가 권한 문제인지, 경로 문제인지, 서버 문제인지, API초과 문제인지등 원인을 파악하기 힘든 에러였습니다.

구글링 결과 삭제된 메시지에 접근하려고 할 때 생기는 문제같음.
구글링 결과 다행인점은 10008에러라는 사례가 있다는 점, 아쉰운점은 나와의 케이스가 동일하지 않다는 점은(나는 메시지접근, 스택오버플로우는 메시지 삭제 또는 편집)
https://stackoverflow.com/questions/64160479/404-not-found-error-code-1008-unknown-message
The problem is, The message id that you inputted has already been deleted. If you want to delete the author message just do

다른 사람들의 경우 이미 삭제된 메시지를 다시 삭제하거나 편집하려는 등 접근할때 문제가 생긴것같음. 나의 경우는 삭제된 메시지에 fetch 를 시도해서 그런거 같음.
해당 에러만 예외 처리해보기

                let starterMessage;
                try {
                    starterMessage = await thread.fetchStarterMessage();
                } catch (error) {
                    console.error(`Failed to fetch starter message for thread ${thread.name}:`, error.message);
                    continue; // Skip to the next thread
                }


  },
  {
    "threadId": "120000000000000",
    "threadName": "GPT...?",
    "threadLink": "https://discord.com/channels/120000000000000/120000000000000",
    "creationDate": "2024-10-14T07:24:32.038Z",
    "weekNumber": -2,
    "mainPostReactions": 62,
    "messageCount": 74,
    "totalReactions": 192,
    "author": "umnnsnsn"
  }
]
...
+++++++++++++++++ start saveCurrentData ++++++++++++++++

결과 성공. 즉 일단을 구현을 위해 예외처리 함으로써 문제가 발생하는 쓰레드는 제외하겠끔 수정했습니다.
검증으로 받은 데이터중에서 해당 채널에서 가장 오래된 포스트임을 확인 완료.
즉 가장 최신 부터 가장 오래된 포스트까지 데이터가 저장된것 확인.