지난 이야기
https://koilsdevelopment.tistory.com/4
디스코드 봇 만들기 대작전 (2) - 메시지 출력 응용
지난 이야기 https://koilsdevelopment.tistory.com/3 디스코드 봇 만들기 대작전 (1) - 메시지 출력 https://koilsdevelopment.tistory.com/2 디스코드 봇 만들기 대작전 (0) - 준비 개발 블로그의 첫 게시글은 디스코드
koilsdevelopment.tistory.com
이전 화에서 다양한 메시지를 출력시키는 명령어를 만들어보았다. 이제 명령어를 자유롭게 만들 수 있기 때문에 슬슬 명령어의 목록을 출력하는 기능이 필요해졌다. 물론 봇 공유 페이지나 클라이언트에게 명령어의 목록을 임의로 보내는 방법이 있지만, 무엇보다도 그 방법은 멋지지 않다.그래서 나는 디스코드 내에서 자체적으로 명령어의 목록이 나오게끔 만들어보고자 한다.
0. 추가 권한 및 아이디
명령어 목록을 출력하기 위해 우리는 추가적인 사전 작업을 진행해야 한다.
먼저 서버 아이디를 가져와보자. 관리자 페이지에 들어간 뒤, 본인의 어플리케이션에 들어간다. 좌측 메뉴에서 OAuth2->URL Generator 메뉴에 들어간 뒤, 이미지와 같이 권한을 설정하고 서버에 봇을 들여온다.
이렇게 되면 명령어 목록을 출력하기 위한 권한을 얻어올 수 있다. 다음은 서버 ID가 필요하다. 구 강좌들을 보면 서버에 우클릭만으로도 서버 ID를 얻어올 있다고들 하는데, 디스코드가 업데이트되서 그런지 서버 ID를 얻어오는 방법이 조금 달라졌다.
우선 서버를 우클릭한다. 그러면 다양한 탭이 나올 텐데, 우리는 서버 설정을 클릭해서 설정 창에 진입한다.
다음으로 여러 메뉴 중 위젯메뉴에 접근하면 해당 서버의 아이디를 얻을 수 있다. 어딘가에 복사해두자.
참고로 이런 방식이면 특정 서버에서만 봇을 사용할 수 있다는 단점이 존재하는데, 서버별로 유동적으로 봇을 설정하려면 봇 업로딩 사이트 같은 곳에 업로드 해야될 듯 싶다. 물론 아직 확실한 방법이 아니라 추측일 뿐이고, 아직은 봇을 어딘가에 공개적으로 업로드할 계획은 없으니 일단 접어두겠다.
1. 명령어 목록 만들기
지금부터는 봇이 가진 명령어의 목록을 구현하는 방법을 알아보고자 한다.
여담이지만 이 기능을 구현하기 위해 정말 별의 별 짓을 다 했기 때문에 시행착오의 과정도 적어보고자 한다. 물론 과정 그대로 다 보여줄 수도 있겠지만, 과정 필요없고 결과만 필요한 사람을 위해 과정은 접어두겠다.
1.5 고군분투의 과정
우선, 코드를 다음과 같이 작성했다.

기존 코드와의 차이점이라면,
@mybot.command()
에서
@mybot.tree.command()
의 형식으로 코드가 바뀌었다. 이전과의 차이점이라면 이게 전부다.
쉽게 말하자면 일련의 커맨드 트리에 내가 넣고자 하는 커맨드를 하나하나 집어넣어준다고 이해하면 될 듯 싶다.
그리고 나는 호기로운 마음으로 코드를 실행했다.

에러. 나는 여기서 지식이 늘었다. 명령어는 소문자로만 해야 하는구나.
나는 모든 명령어를 소문자로 바꿔준 뒤, 다시 실행한다.
"이번에는 무조건 되겠지? 이번 기능도 껌이구만~"
우매함이 가득한 생각이었다.

에러. 갑자기 기존 코드에서 에러가 발생했다. 이유는 일부 명령어의 매개변수의 자료형 지정이 안되어있다는 것. 이전에는 잘 작동했는데 갑자기 안먹히는걸 보면 분명 트리 형식이 문제일 것이리라.
나는 해당 코드를 다음과 같이 수정했다.

아마 이렇게 자료형을 지정하면 잘 작동할 것이다.
물론 여러 개의 가변 매개변수를 받기 위해 *args를 사용했었기 때문에 이런 방식이 썩 달갑지는 않았지만, 아무렴 코드만 실행되면 상관없다고 생각했다. 그리고 실행.

에러. 이번에는 지원하지 않는 자료형이란다. 아무래도 여기서부턴 깊이 파고들어야 할 텐데, 일단 이 문제는 접어두는 편이 낫겠다고 판단하여 대충 코드를 마무리지었다.

이러면 이제 실행 자체는 가능하겠지. 나는 호기로운 마음으로 코드를 실행한다.
실행완료. 일단 오류는 발생하지 않은 것 같다.
"이제 제대로 작동하겠지?"
이번에는 정말 제대로 작동하리라는 마음을 가지고 명령어를 입력해보았다. 허나 난 저 말이 하나의 클리셰로 작동함을 인지하지 못했다.

명령어 목록이 나오지 않는다. 원래대로라면 /hello 커맨드가 제대로 출력되어야 할 텐데, 아무것도 나오지 않는다. 그래도 명령어 자체는 작동할 것이라는 마음으로 /getarg를 입력했다.

에러. 이제는 명령어마저 제대로 작동하지 않는다. 위의 에러는 명령어를 찾을 수 없다는 뜻이다. 분명 *arg 고쳐먹겠다고 여러 번을 수정한 명령어인데 이젠 인식 자체를 못한다.
나는 명령어 목록에 대해 자료를 더 찾아본다. 그리고 나는 강좌를 하나 찾아낸다.
https://kante-kante.tistory.com/43
Examples - 디스코드 봇 길드 기본(discord py)
Examples - Discord Guild 기본 https://github.com/Rapptz/discord.py/blob/master/examples/app_commands/basic.py 원본 코드는 위 페이지를 참조. 실행 전 설정 사항 1. dico_token.py 파일 내부에 길드 ID 정보 추가(서버 ID) 디스
kante-kante.tistory.com
여기서 서버 아이디를 지정해야 한다는 말이 적혀있었다. 아! 초반 선언부터 차이가 있구나! 기존의 방식은 discord.ext에 존재하는 커맨드를 이용해 명령어를 추가해야 했다면, 트리를 구성하기 위해선 discord의 app_commands라는 곳에서 초기 설정을 해야 한다는 것이다.
나는 바로 강좌대로 내 코드를 작성하기 시작했다.

확실히 코드도 수정했고, 이후 mybot=discordClient(intents=discord.Intents.all())의 방식으로 설정했다. 이제는 두려울 게 없었다. 이번에는 무조건 실행시키리라는 사명감을 가지고 나는 코드를 실행한다.

에러. 이번엔 str 형식이 아닌 id의 형식으로 코드를 작성하란다. 처음에 나는 이 문장을 이해하지 못해서 별의 별 짓을 다 하게 된다.

sync를 설정하지 않아서 그런가 싶어 코드 구동시에 sync 관련 검사를 하는 코드를 작성해보기도 했고,

아예 sync를 비활성화시켜 강제적으로 검사를 진행하게도 해보았다. 참고로 guild_id='서버 아이디'의 형식으로 이전에 초기화를 해뒀다.

아무튼, 이런저런 시도를 하다보니 갑자기 봇이 작동하기 시작한다. 분명 문제는 아직 해결하지는 못한 것 같은데 일단 실행이 되니 나는 바로 실전 테스트에 들어간다.
이때까지만 해도 코드가 제대로 작동할 것이라는 기대감을 품고 있었다.

역시나, 봇은 묵묵부답이다.
"아니, 도대체 이유가 뭔데!!!!"

분노의 타이핑이 시작된다. 아무리 명령어를 입력해봐도 명령어 목록은 죽어도 나오지 않고, 엎친데 덮친 격으로 기존에 짜둔 명령어도 제대로 작동하지 않았다. 나는 고뇌했다. 아무래도 아이디를 str이 아닌 id라는 형식으로 넘겨줘야 될 것 같은데, 도저히 어떻게 해야 할 지 방법이 생각나질 않았다. 이 순간, 갑자기 무언가가 내 머리를 스쳐 지나갔다.
....GPT?
인류 문명의 기술력은 발전에 발전을 거쳐, 수많은 입력을 기반으로 다양한 정보를 축적한 AI를 만들어내는데 성공했다. 그 똑똑한 가상 인공지능 척척박사에게 내 고민을 토로해보면 분명 쓸모있는 대답을 받을 수 있을거야.
여담이지만 나는 챗GPT를 일종의 최후의 수단으로서 사용하고 있다. AI가 제대로 된 대답을 주지 않을 가능성도 있을 뿐더러 가능한 한 내 실력대로 공부하면서 성장하고 싶었기 때문이다. 하지만 지금 이 순간, 나는 인류 문명의 기술력에 굴복해버리고 말았다.

역시나 AI는 탁월한 해답을 알려줬다. id를 필요로 한다면 id를 주면 된다는 것. 다만 저 discord.utils.get 부분은 그 어떤 강좌에서도 찾아낼 수 없던 새로운 문법이었다. 아무래도 강좌가 아니라 API를 뜯어봐야 했던건 아니었을까 고민해본다.
AI님의 말씀대로 코드를 작성 후, 조심스럽게 실행해본다. 일단 봇 자체는 작동되기 시작했다. 이제 명령어 목록만 나오면 좋겠는데... 나는 두 눈을 꼭 감고, 내 명령어를 신중하게 입력해본다.

드디어.
나는 내 두 눈을 의심했다. 드디어 명령어의 목록이 제대로 나오기 시작하는 것이다!!!!
여기까지가 온전한 기능을 구현하기 위한 여정이었다. 이후부터는 예제코드를 보며 코드를 분석해보기로 하자.
다음은 예제코드다.
import discord
import random
from discord.ext import commands
mybot=commands.Bot(command_prefix='!', intents=discord.Intents.all())
# 봇 동기화
@mybot.event
async def on_ready():
mybot.sync=False
guild_id=discord.utils.get(mybot.guilds, id=서버 아이디)
mybot.tree.copy_global_to(guild=guild_id)
await mybot.tree.sync(guild=guild_id)
print('Bot is Working Now!')
# 명령어 선언
@mybot.tree.command(name='hello', description='Say hello Yeah!')
async def hello(inter: discord.Interaction):
await inter.response.send_message("Hello Discord!")
@mybot.tree.command(name='arguments', description='Get Some Arguments')
async def getarg(inter: discord.Interaction, *, args: str):
await inter.response.send_message(args)
@mybot.tree.command(name='dice', description='Roll the dice')
@discord.app_commands.describe(num='input any number upper 0',)
async def dice(inter: discord.Interaction, num: int):
if num>0:
result=random.randrange(0, num)
await inter.response.send_message(result+1)
# 코드 실행
mybot.run('서버 토큰')
지금까지 짜온 코드에서 명령어 목록을 만들기 위한 코드를 추가했다.
실행 결과는 다음과 같다.
이제 코드를 분석해보자.
2. 코드 분석
명령어 목록 동기화
@mybot.event # 봇의 이벤트를 다룸
async def on_ready(): #봇 작동 준비 완료시 실행
mybot.sync=False #봇 동기화 비활성화
guild_id=discord.utils.get(mybot.guilds, id=서버 아이디) #봇이 속해있는 서버 아이디를 가져옴
mybot.tree.copy_global_to(guild=guild_id) #해당 서버에서 명령어를 사용할 수 있게 설정
await mybot.tree.sync(guild=guild_id) #해당 서버에서 명령어 동기화
print('Bot is Working Now!')
해당 코드를 통해 봇과 서버를 동기화시킨다.
추가된 코드만 분석해보자.
mybot.sync=False
봇에 설정되어있는 동기화를 비활성화한다.
guild_id=discord.utils.get(mybot.guilds, id=서버 아이디)
guild_id라는 임의의 변수에 서버 아이디를 가져온다. 이때 자료형은 id로 저장된다.
mybot.tree.copy_global_to(guild=guild_id)
guild_id가 가리키는 서버에서 명령어를 사용할 수 있게 설정한다.
await mybot.tree.sync(guild=guild_id)
guild_id가 가리키는 서버에 명령어를 동기화한다.
목록에 명령어 추가
@mybot.tree.command(name='dice', description='Roll the dice')
@discord.app_commands.describe(num='input any number upper 0',)
async def dice(inter: discord.Interaction, num: int):
if num>0:
result=random.randrange(0, num)
await inter.response.send_message(result+1)
위와 같은 방식을 반복하여 명령어를 계속 추가할 수 있다.
주요 코드를 분석해보자.
@mybot.tree.command(name='dice', description='Roll the dice')
@mybot.tree.command는 명령어 트리에 아래 명령어를 추가하겠다는 선언이다. 매개변수로 name과 description이 있다. name은 명령어의 이름, description은 명령어의 설명을 입력한다.
적용 결과는 아래와 같다.
@discord.app_commands.describe(num='input any number upper 0',)
@discord.app_commands.describe는 명령어의 추가 입력칸에 대한 설명을 설정한다. 매개변수는 추가하고자 하는 입력 명령 변수명, 그리고 해당 값에 대한 설명을 적어넣어주면 된다.
적용 결과는 아래와 같다.
async def dice(inter: discord.Interaction, num: int):
위의 describe문에서 변수를 추가했어도, 명령어 함수 선언부에서 매개변수를 설정해줘야 한다.
그래야 코드 내에서 매개변수를 온전히 다룰 수 있다.
또한, 기존의 ctx(==Context)대신 inter(==discord.Interaction)를 이용해 메시지 관련 처리를 한다.
await inter.response.send_message(result+1)
discord.Interaction의 메시지 출력 방법은 다음과 같다.
이렇게 하면 명령어 입력 시, 명령어 선언자를 멘션하며 결과를 출력한다.
오늘 기능을 구현하기 위해서 조금 고군분투했다. 이전 방식대로 !나 . 등 임의의 코드를 입력하게 하는 식으로 명령어를 선언하게 하면 코드가 쉽게 짜여지긴 하겠지만 명령어 목록이 디스코드 내에서 나오지 않는 단점이 있다. 반대로 오늘처럼 명령어 목록이 나오게 구현한다면 외관상으론 보기 좋겠지만 명령어 선언은 /(슬래쉬)로밖에 사용할 수 없다. 각자 장단점이 뚜렷하다는 것을 알았으니, 상황에 따라 유동적으로 선택하면 될 듯 싶다.
참고한 링크
https://kante-kante.tistory.com/43
Examples - 디스코드 봇 길드 기본(discord py)
Examples - Discord Guild 기본 https://github.com/Rapptz/discord.py/blob/master/examples/app_commands/basic.py 원본 코드는 위 페이지를 참조. 실행 전 설정 사항 1. dico_token.py 파일 내부에 길드 ID 정보 추가(서버 ID) 디스
kante-kante.tistory.com
https://www.reddit.com/r/Discord_Bots/comments/yw7iwl/working_with_discordpy_commands/
From the Discord_Bots community on Reddit: Working with discord.py commands
Explore this post and more from the Discord_Bots community
www.reddit.com
https://dev.to/mannu/4slash-commands-in-discordpy-ofl
#4.Slash Commands in discord.py
Hey Fellows! I am back with another post and this time we will learn about: 1.Slash...
dev.to
https://gist.github.com/AbstractUmbra/a9c188797ae194e592efe05fa129c57f
discord.py 2.0+ slash command info and examples
discord.py 2.0+ slash command info and examples. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
https://www.linkedin.com/pulse/discord-bot-part-2-slash-commands-leandro-fumio-kino
Discord Bot - Part 2: Slash Commands
Hello! Let's check how we can create our own bot slash commands. In the previous article, we created our application and created our happy-greeter bot with the necessary permissions to read messages and use slash commands.
www.linkedin.com
https://discordpy.readthedocs.io/en/stable/interactions/api.html?highlight=app_commands#interaction
Interactions API Reference
discordpy.readthedocs.io
https://discordpy.readthedocs.io/en/stable/api.html#client
API Reference
Loads the libopus shared library for use with voice. If this function is not called then the library uses the function ctypes.util.find_library() and then loads that one if available. Not loading a library and attempting to use PCM based AudioSources will
discordpy.readthedocs.io
디스코드 봇 슬래시 커맨드 사용하기
보통 슬래시 커맨드를 검색하면 이런식으로 설치하라고 말한다. 하지만 내가 했을 때는 계속해서 라고 떠서 사용할 수 없었다. 계속 찾다가 쓰는 방법을 발견해서 올린다. 준비 먼저 슬래시 커
velog.io
https://stackoverflow.com/questions/71165431/how-do-i-make-a-working-slash-command-in-discord-py
How do i make a working slash command in discord.py
I am trying to make a slash command with discord.py I have tried a lot of stuff it doesn't seem to be working. Help would be appreciated.
stackoverflow.com
'프로그래밍 > Discord Bot' 카테고리의 다른 글
디스코드 봇 만들기 대작전 (4) - 24시간 호스팅 (koyeb) (2) | 2024.01.15 |
---|---|
디스코드 봇 만들기 대작전 (2) - 메시지 출력 응용 (1) | 2024.01.10 |
디스코드 봇 만들기 대작전 (1) - 메시지 출력 (0) | 2024.01.09 |
디스코드 봇 만들기 대작전 (0) - 준비 (1) | 2024.01.09 |