파이썬으로 간단한 디스코드 봇 만들기 시리즈 세 번째 포스팅이다. 이번엔 봇에게 작업을 시키기 위한 명령어를 만들어보자.

 

봇이라면 일을 했으면 좋겠는데

지금까지 우리가 만든 봇은 그냥 무언가 메시지가 채팅창에 보이면 “그렇군요!” 라고 대답하는 것 밖에 하지 않는다. 그런데 다른 사람들이 만든 봇을 보면 뭔가 명령어 같은걸 만들어서 봇에게 작업을 시키기도 한다.

  • 음악을 골라준다거나
  • 전적 검색을 해 준다거나
  • 기타 해당 서버에서 필요한 동작을 시켜 준다거나

우리는 단순히 아무말에나 “그렇군요!” 라는 말을 하게 할 수도 있지만, 기왕이면 무언가 명령을 내려서 봇이 우리가 원하는 작업을 시키게 하면 더더욱 봇이 유용해질 것이다.

이번 포스팅에서는 봇에게 주사위를 굴려서 임의의 값을 하나 고르도록 하는 명령을 만들어보자.

roll the dice

 

Bot 객체

discord.py에는 봇을 위한 클래스가 따로 있다. 바로 Bot 클래스이다. Bot 클래스는 BotBase와 Client 클래스를 상속받기 때문에, 기존의 Client 클래스의 동작도 소화할 수 있도록 도와준다.

그러면 기존의 코드를 Client 객체가 아닌 Bot 객체를 이용하도록 바꿔보자. 다음과 같이 작성하면 된다.
그리고 on_message 함수는 빼버리자. 이제 이 함수는 쓸 일이 없다.

 

from discord.ext import commands


# 개발자 페이지에서 봇에 대한 토큰 문자열을 가져온 뒤, TOKEN에 대입하자
TOKEN = "봇 페이지에서 받은 토큰 문자열"

client = commands.Bot(command_prefix='/')


@client.event
async def on_ready():
    print(f'{client.user} online!')


client.run(TOKEN)

 

그럼 코드를 살펴보자.

from discord.ext import commands

import discord에서 뭔가 복잡해졌다. discord.ext는 discord.py 라이브러리에서 제공하는 확장 기능이 들어있는 패키지로, 여기에 commandstask라는 추가적인 기능이 들어있다. 우리는 지금 봇에게 내리는 명령에만 관심이 있으므로, 명령어를 편하게 정의할 수 있도록 도와주는 모듈인 commands만 import 하도록 하자.

client = commands.Bot(command_prefix='/')

discord.Client()에서 commands.Bot(...)으로 변경되었다. 이제 Client 객체 대신 Bot 객체를 사용할 것이기 때문에 다른 객체를 생성하도록 코드를 수정하였다.

 

수정된 부분이라고는 위의 두 가지 뿐이다. 그리고 이 코드를 실행한다면 여전히 우리가 만든 봇은 잘 들어올 것이다.

 

명령 만들기

그러면 이제 주사위를 굴리는 명령을 만들어보자. 그러려면 명령이 어떻게 동작할 지에 대해 설계를 해야한다.

주사위를 굴리는 기능이 들어있는 월드 오브 워크래프트에서는 명령어가 이렇게 되어있다.

/주사위 숫자

위의 명령어를 입력하면 1부터 입력한 숫자 까지의 수 중 하나를 임의로 선택해서 돌려주게 된다. 이런 식의 명령어를 우리도 가져다 쓰면 될 것 같다. 우리도 똑같은 명령을 만들어보자.

그러면 차근차근 어떻게 명령을 만들어보자.

 

  • 봇에게 명령어 여부를 어떻게 인식시킬 것인가

디스코드 봇에게 명령을 내리기 위해서는 채팅창에서 명령을 내려야 한다. 그리고 이 채팅이 명령을 내리는 채팅임을 알 수 있어야 한다. 즉, 일반적인 채팅과는 구분되는 무언가가 있어야 한다는 의미이다.

그래서 존재하는 것이 이것이다.

client = commands.Bot(command_prefix='/')

Bot 객체를 생성할 때 들어가는 키워드 인자, command_prefix가 바로 명령어 접두사를 결정하는 인자이다. 즉, 이 인자를 사용하여 이걸로 시작하는 모든 채팅은 명령어로 간주해라! 라고 봇에게 알려줄 수 있다.
이 코드에서 전달한 문자열은 무엇인가? 슬래시 하나다. 이제 이 봇은 슬래시로 시작하는 모든 채팅을 명령어로 간주할 것이다.

슬래시(/)는 디스코드에서 지원하는 명령에도 사용하므로, 봇 명령과 겹치지 않게 주의해야 한다.

 

  • 이제 어떤 명령을 시킬 것인가

’/’가 들어가면 명령어로 간주하도록 했다. 그러면 이제 주사위 명령은 어떻게 인식시킬까? 다음 코드를 추가하면 된다.

 

@client.command(name='주사위')
async def roll(ctx):
    await ctx.send('주사위를 굴립니다')

 

인자가 들어있는 데코레이터로 명령을 등록하는 방식이다. command 함수 안에 키워드 인자 name으로 명령어의 이름을 결정할 수 있다. 여기서는 명령어의 이름을 주사위로 결정했다. 따라서, /주사위라는 명령이 들어오면, 아래의 roll 함수가 호출될 것이다.

roll 함수에 ctx라는 매개변수가 눈에 보일 것이다. 이 인자에는 Context 객체가 들어온다.. Context객체로 send()를 호출하면, 명령어를 호출한 채팅창에 메시지를 전송하게 된다.

Context 객체 안에는 명령과 관련된 많은 정보가 담겨있다. 위에서 링크된 문서를 읽어보면서 내가 필요로 하는 것이 있는지 확인해보자.

 

이제 이 코드를 실행해보자.

from discord.ext import commands


# 개발자 페이지에서 봇에 대한 토큰 문자열을 가져온 뒤, TOKEN에 대입하자
TOKEN = "봇 페이지에서 받은 토큰 문자열"

client = commands.Bot(command_prefix='/')


@client.event
async def on_ready():
    print(f'{client.user} online!')


@client.command(name='주사위')
async def roll(ctx):
    await ctx.send('주사위를 굴립니다')


client.run(TOKEN)

그리고 채팅창에 /주사위 라고 치면 다음과 같은 화면을 볼 수 있을것이다.

roll result

 

  • 명령에 인자는 어떻게 받아들이는가

이제 우리가 만든 봇은 /주사위까지는 인식을 하고, 그 결과로 주사위를 굴리는 듯한 행동을 보여줄 수 있게 되었다. 이제 실제로 주사위를 굴릴 수 있도록 하면 이 명령은 완성이 될 것이다.

우리가 만들고자 하는 명령은 다음과 같이 생겼다.

/주사위 숫자

이제 숫자만 받아들이면 되는데, 어떻게 받아들일까? roll 함수를 다음과 같이 수정하자.

@client.command(name='주사위')
async def roll(ctx, number):
    await ctx.send(f'1에서 {number} 까지의 주사위를 굴립니다')

roll 함수에 새로운 인자, number가 추가되었다. 이렇게 하면 /주사위 다음에 들어오는 텍스트가 number에 들어가게 된다!

getting argument

그리고 이 텍스트들은 공백문자, ‘ ‘로 구분된다. 따라서 다음과 같이 공백 문자로 여러 텍스트가 구분이 되면, 맨 앞의 텍스트 하나만 들어가게 된다.

getting argument

이제 주사위의 값을 받아올 수 있게 되었으니, 1에서 number까지의 수 중 하나를 임의로 골라오는 함수를 하나 추가하자. 파이썬에서 기본으로 제공하는 random 모듈에서 randint()를 사용하면 될 것이다.

그러면 다음과 같이 코드를 작성해보자.
number는 문자열 객체인 str로 들어오니, 정수형으로 변환해서 사용할 수 있도록 하자.

from discord.ext import commands

import random

# 개발자 페이지에서 봇에 대한 토큰 문자열을 가져온 뒤, TOKEN에 대입하자
TOKEN = "봇 페이지에서 받은 토큰 문자열"

client = commands.Bot(command_prefix='/')


@client.event
async def on_ready():
    print(f'{client.user} online!')


@client.command(name='주사위')
async def roll(ctx, number):
    await ctx.send(f'주사위를 굴려 {random.randint(1,int(number))}이(가) 나왔습니다. (1-{number})')


client.run(TOKEN)

그러면 /주사위 숫자를 실행하면 임의의 숫자가 나올 것이다.

getting argument

 

아니면 number에 정수형이 들어와야 한다는 것을 다음과 같이 명시할 수도 있다.

@client.command(name='주사위')
async def roll(ctx, number: int):
    await ctx.send(f'주사위를 굴려 {random.randint(1,number)}이(가) 나왔습니다. (1-{number})')

 

  • 명령에 잘못된 인자가 들어오면 어떻게 하는가

그런데 하나 생각해야 할 것이 있다. /주사위뒤에 반드시 숫자가 온다는 보장이 없다.

주사위 명령어를 잘못 이해해서 /주사위 육이라고 사용하는 사람이 있을 수도 있고, 오타를 내서 /주사위 6ㅛ이라고 들어갈 수도 있다. 아니면 그냥 /주사위만 집어넣을 수도 있다. 만약 위와 같은 명령어가 들어간다면, 봇은 오류 메시지를 콘솔 상에 출력하고 아무것도 하지 않을 것이다.

getting argument

사실 잘못된 명령어가 들어간다고 봇이 죽거나 하진 않지만, 기왕이면 사용자에게 명령어를 잘못썼다고 알려주고 싶다. 그러면 예외상황이 발생했다는 것을 어떻게 알 수 있을까?

roll 함수 밑에 다음 코드를 추가하면 된다.

@roll.error
async def roll_error(ctx, error):
    await ctx.send("명령어 잘못썼어!")

만약 roll 함수를 수행 도중 또는 잘못된 인자가 들어왔을 때, 위의 roll_error 함수를 호출하도록 할 수 있다.

error라는 매개 변수가 눈에 띈다. 여기에는 어떤 예외가 발생했는지에 대한 정보가 들어있다. 여기에 들어온 error 정보를 기반으로, 어떻게 처리할 지를 결정할 수 있게 된다.

그러면 이 코드를 추가하고 다시 한 번 잘못된 명령어를 전달해보자. 그러면 다음과 같은 화면을 볼 수 있을것이다.

getting argument

이제 여기서 error를 통해 전달된 예외로 따로따로 세부적인 설명을 할 지, 아니면 하나로 퉁 쳐서 “명령어는 이렇게 쓰세요!” 라고 안내를 할 지는 여러분이 결정하면 된다. 여기서는 그냥 하나의 안내 문구로 “2 이상의 숫자를 넣어주세요!” 와 함께 예시를 보여주는 것으로 퉁치도록 한다.

그러면 지금까지 한 최종 코드를 보도록 하자.

from discord.ext import commands

import random

# 개발자 페이지에서 봇에 대한 토큰 문자열을 가져온 뒤, TOKEN에 대입하자
TOKEN = "봇 페이지에서 받은 토큰 문자열"

client = commands.Bot(command_prefix='/')


@client.event
async def on_ready():
    print(f'{client.user} online!')


@client.command(name='주사위')
async def roll(ctx, number: int):
    await ctx.send(f'주사위를 굴려 {random.randint(1,number)}이(가) 나왔습니다. (1-{number})')


@roll.error
async def roll_error(ctx, error):
    await ctx.send(f"2 이상의 정수를 넣어주세요!\nex) /주사위 6")


client.run(TOKEN)

 


 

이제 명령을 만드는 방법을 알았다. 이제 어떤 명령을 만들어서 봇 사용자에게 편리함을 제공할지는 당신의 능력에 달렸다.

다음에는 봇을 24시간 돌릴 수 있는 방법을 알아보자.

 

Reference : 이 포스팅은 다음 사이트를 참고하여 작성하고 있다.

https://realpython.com/how-to-make-a-discord-bot-python/
https://discordpy.readthedocs.io/en/latest/

 

Comments