달나라 노트

Python Discord : Python으로 Discord bot 만들기 02. 주사위 굴리기 bot 만들기 (discord ext commands 사용) 본문

Python/Python ETC

Python Discord : Python으로 Discord bot 만들기 02. 주사위 굴리기 bot 만들기 (discord ext commands 사용)

CosmosProject 2022. 2. 19. 19:51
728x90
반응형

 

 

 

이번에는 Python으로 주사위를 굴려서 랜덤한 숫자를 return해주는 bot을 만들어봅시다.

 

이 글을 읽기 전에 discord bot에 관한 배경 지식이 없거나 기초적인 셋팅을 하지 않은 상태라면 아래 링크를 먼저 참고하면 좋습니다.

https://cosmosproject.tistory.com/384

 

 

 

from discord.ext import commands

discord_token = 'discord_token_string'

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

@client.event
async def on_ready():
    print('{} logged in.'.format(client))
    print('Bot: {}'.format(client.user))
    print('Bot name: {}'.format(client.user.name))
    print('Bot ID: {}'.format(client.user.id))


client.run(discord_token)

이번에는 discord library에서 ext commands를 사용할 것입니다.

ext는 discord library에서 제공하는 확장 기능을 담고있는 것이라고 이해하면 됩니다.

 

 

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

commands.Bot 객체를 client에 할당하고 있습니다.

이것이 discord.ext.commands 객체를 사용하는 시작입니다. 굳이 commands 객체를 사용하는 것은 discord channel에서 사용자들이 특정 메세지(명령어)를 입력하면 discord bot이 반응하게하도록 할 것이기 때문입니다.

그리고 인자로서 command_prefix='/' 라는 내용을 전달하고 있습니다.

이 말은 discord 채널에서 bot에게 전달할 명령어를 적을 때 그 명령어의 접두어(prefix)는 슬래쉬(/)라는 것을 의미합니다.

즉, discord 채널에서 bot을 이용할 명령어는 /주사위, /시간 등 슬래쉬(/)로 시작하는 문자를 적어야 한다는 의미입니다.

 

 

on_ready 함수는 이전 discord bot 관련 글에서 보았던 내용입니다.

간단하게 설명하자면 discord bot이 정상적으로 discord channel에 접속하면 실행되는 함수가 on_ready 함수다 라고 이해하면 됩니다.

 

 

 

 

 

 

from discord.ext import commands
import numpy as np

discord_token = 'discord_token_string'

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


@client.event
async def on_ready():
    print('{} logged in.'.format(client))
    print('Bot: {}'.format(client.user))
    print('Bot name: {}'.format(client.user.name))
    print('Bot ID: {}'.format(client.user.id))


@client.command(name='주사위')
async def roll(ctx, number):
    await ctx.send('Roll dice from 1 to {}'.format(number))

    result_number = np.random.randint(1, int(number)+1)
    await ctx.send('Result = {}'.format(result_number))



client.run(discord_token)

roll 함수를 추가했습니다.

 

roll 함수를 봐봅시다.

...

@client.command(name='주사위')
async def roll(ctx, number):
    await ctx.send('Roll dice from 1 to {}'.format(number))

    result_number = np.random.randint(1, int(number)+1)
    await ctx.send('Result = {}'.format(result_number))

...

 

- @client.command(name='주사위')

- async def roll(ctx, number):

client.command decorator를 생성하고 바로 다음 줄에 오는 함수(roll 함수)를 decorating하기 위한 부분입니다.

command의 인자로서 name='주사위'라는 인자를 받습니다.

이 말인 즉슨 prefix로 슬래쉬(/)를 가지고 그 뒤에 주사위라는 문자를 가진 명령어가 입력되면 실행한다는 의미입니다.

/주사위 라고 채널에 치면 decorating된 함수(바로 다음 줄에 나온 함수)가 실행되는 것이죠.

 

 

roll 함수는 2개의 인자를 받습니다.

첫 번째 인자인 ctx 매개변수는 context 객체를 의미합니다. client.command로 decorating되는 거의 모든 함수에서 가장 첫 번째 인자로 전달하는 것이 context 객체입니다.

context 객체는 discord bot이 채널에 메세지를 보내는 것 등 discord bot이 채널과 소통할 수 있는 여러 기능을 담은 객체입니다. context 객체에 대한 자세한 내용은 아래 링크를 참고하면 됩니다.

https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#context

 

number는 명령어에 추가로 붙는 인자를 의미합니다.

예를들어 다음과 같은 명령어를 discord 채널에 입력한다면 number는 6이 됩니다.

/주사위 6

 

위처럼 discord 채널에 명령어(/주사위)를 입력한 후 공백을 한 칸 띄워주고 command로 decorating되는 함수의 추가 인자를 전달할 수 있습니다.

 

 

 

- await ctx.send('Roll dice from 1 to {}'.format(number))
- result_number = np.random.randint(1, int(number)+1)
- await ctx.send('Result = {}'.format(result_number))

그리고 위 부분은 roll 함수가 어떤 내용들을 실행시킬지에 대한 부분입니다.

 

- await ctx.send('Roll dice from 1 to {}'.format(number))

이 부분은 1부터 number까지의 숫자를 가진 주사위를 돌리겠다는 메세지를 discord 채널에 전송합니다.

 

- result_number = np.random.randint(1, int(number)+1)

이 부분은 numpy의 randint method를 이용해서 1부터 number까지의 범위 중 랜덤한 정수를 추출하도록 합니다.

한 가지 주의할 것은 /주사위 10이라는 명령어를 입력했을 때 10이 number라는 인자로 전달되게 됩니다.

근데 이렇게 전달되는 인자는 모두 텍스트입니다. 10이라고해서 숫자가 아니기 때문에 int method를 이용해서 텍스트로 전달된 number를 숫자로 바꿔준겁니다.

 

- await ctx.send('Result = {}'.format(result_number))

이 부분은 정해진 random하게 정해진 숫자를 결과로서 discord channel에 출력해줍니다.

 

 

여기까지 코드를 작성한 후 실행합니다.

그리고 discord channel에 위처럼 /주사위 10을 입력하면 discord bot이 1부터 10까지의 숫자 중 랜덤하게 하나를 정해서 출력해줍니다.

 

 

 

 

 

 

 

근데 뭔가 좀 부족합니다.

누구나 채널에 존재하는 모든 명령어를 알고 있는 것은 아니기에 명령어의 종류를 알려주면 더 좋겠죠?

 

이제 명령어의 종류를 알려주는 기능도 추가해봅시다.

 

 

from discord.ext import commands
import numpy as np

discord_token = 'discord_token_string'

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


@client.event
async def on_ready():
    print('{} logged in.'.format(client))
    print('Bot: {}'.format(client.user))
    print('Bot name: {}'.format(client.user.name))
    print('Bot ID: {}'.format(client.user.id))


@client.command(name='주사위')
async def roll(ctx, number):
    await ctx.send('Roll dice from 1 to {}'.format(number))

    result_number = np.random.randint(1, int(number)+1)
    await ctx.send('Result = {}'.format(result_number))


@client.command(name='bot?')
async def introduce_commands(ctx):
    dict_commands = {
        'roll': '/주사위 [2이상의 정수] (e.g. /주사위 5 또는 /주사위 10)',
    }

    for k, v in dict_commands.items():
        await ctx.send('{}'.format(v))



client.run(discord_token)

명령어의 종류를 알려주기 위해서 어떤 사용자가 discord channel에 /bot?이라는 내용을 입력하면 명령어를 출력하도록 해줄 예정입니다.

 

그래서 introduce_commands라는 함수를 추가했습니다.

전체적인 흐름은 roll 함수와 비슷합니다.

 

- @client.command(name='bot?')

/bot?이라는 명령어가 discord channel에 입력되면 동작할 무언가를 설정할 것이기 때문에 command의 인자로서 name='bot?'을 전달했습니다.

 

- dict_commands

discord channel에서 사용되는 모든 명령어 정보를 담은 dictionary를 구성했습니다.

지금은 /주사위라는 명령어 하나 뿐이지만 명령어가 늘어날 경우 이 dictionary에 더 많은 명령어를 기입하고 관리할 수 있습니다.

 

    for k, v in dict_commands.items():
        await ctx.send('{}'.format(v))

dictionary에있는 명령어 정보를 for loop를 통해 하나씩 ctx.send를 이용하여 채팅방에 출력해줍니다.

 

 

여기까지 코드를 실행한 후 discord channel에 /bot? 명령어를 입력해봅시다.

그러면 아래와 같이 명령어 정보가 나오는 것을 알 수 있습니다.

 

 

 

 

 

 

 

 

 

 

명령어 입력 시 number라는 인자를 전달하기 위해 /주사위 10 등의 형태를 가진 명령어를 사용하였습니다.

따라서 위 이미지처럼 /주사위 뒤에 10 5 2 3 등의 여러 숫자를 전달하더라도 가장 첫 번째 인자인 10만 인식되어서 number 인자로 전달된다는 것에 주의합시다.

 

 

만약 하나의 명령어에 여러 개의 인자를 동시에 전달하고싶다면 코드를 아래와 같이 수정해야합니다.

 

@client.command(name='주사위')
async def roll(ctx, number1, number2, number3):
    await ctx.send('{no1}, {no2}, {no3}'.format(no1=number1, no2=number2, no3=number3))

 

roll 함수의 인자를 수정했습니다.

context 객체인 ctx는 동일하지만 그 외의 인자로 총 3개 number1, number2, number3를 적었습니다.

이 말은 /주사위 명렁어 뒤에 총 3개의 인자를 적어서 전달할 수 있다는 의미입니다.

 

roll 함수의 내용 또한 이해하기 쉽게 받은 인자를 그대로 출력해주도록 바꿨습니다.

roll 함수만 위처럼 변경한 후 코드를 다시 실행하면 아래 이미지처럼 /주사위 뒤에 공백으로 구분된 3개의 인자(10, 5, 2)를 정상적으로 받아 출력하는 것을 볼 수 있습니다.

 

 

 

 

 

 

 

 

이제 command error에 대해 알아봅시다.

지금 구성한 주사위 bot의 정상적인 명령어는 다음과 같은 형태입니다.

/주사위 숫자

 

근데 만약 아래와 같이 잘못된 형태의 command를 입력하면 어떻게 될까요?

/주사

/주사위

/ewoirjgsorinfg

 

실제로 위 이미지처럼 잘못된 명령어를 입력할 경우 discord bot은 아무 반응도 하지 않습니다.

 

 

다만 discord bot 코드가 실행되는 콘솔창을 보면 아래와 같은 에러메세지가 발생합니다.

discord.ext.commands.errors.CommandNotFound: Command "woierfhjow" is not found

입력한 woierfhjow라는 command를 찾을 수 없다는 것이죠.

 

 

 

또한 위처럼 /주사위 만 입력하고 뒤에 숫자를 생략해도 discord bot은 아무 반응이 없습니다.

 

다만 discord bot 코드가 실행되는 콘솔창을 보면 아래와 같은 에러메세지가 발생합니다.

discord.ext.commands.errors.MissingRequiredArgument: number is a required argument that is missing.

 

 

 

 

이렇게 잘못된 명령어를 입력하는 경우 discord bot이 종료되거나 뭔가 엄청난 사태가 발생하진 않습니다.

다만 잘못된 명령어를 입력한 경우 명령어가 잘못되었다는 안내를 해주는게 좋겠죠?

 

 

이를 위해 기존 주사위 bot 코드에 roll_error라는 함수를 추가해봅시다.

from discord.ext import commands
import numpy as np

discord_token = 'discord_token_string'

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


@client.event
async def on_ready():
    print('{} logged in.'.format(client))
    print('Bot: {}'.format(client.user))
    print('Bot name: {}'.format(client.user.name))
    print('Bot ID: {}'.format(client.user.id))


@client.command(name='주사위')
async def roll(ctx, number):
    await ctx.send('Roll dice from 1 to {}'.format(number))

    result_number = np.random.randint(1, int(number)+1)
    await ctx.send('Result = {}'.format(result_number))


@roll.error
async def roll_error(ctx, error):
    await ctx.send('Wrong command: {}\nUse /bot? if you need command explanation.'.format(str(error)))


@client.command(name='bot?')
async def introduce_commands(ctx):
    dict_commands = {
        'roll': '/주사위 [2이상의 정수] (e.g. /주사위 5 또는 /주사위 10)',
    }

    for k, v in dict_commands.items():
        await ctx.send('{}'.format(v))



client.run(discord_token)

 

 

 

 

추가된 부분을 아래와 같습니다.

@roll.error
async def roll_error(ctx, error):
    await ctx.send('Wrong command: {}\nUse /bot? if you need command explanation.'.format(str(error)))

 

- @roll.error

먼저 이 부분입니다.

error관련 내용을 다루기 위해선 먼저 어떤 부분에서 error가 발생했는지를 알아야 합니다.

이 부분은 roll 함수에서 error가 발생한 부분에 대한 decorator라는 의미힙니다.

즉, 이 부분은 error가 발생했을 때 바로 다음 줄에서 나올 함수를 실행시킨다 라는 의미입니다.

 

- async def roll_error(ctx, error):

roll_error라는 함수를 생성합니다.

roll_error함수는 어느 함수와 마찬가지로 ctx 라는 context인자를 받습니다.

그리고 error인자를 받는데 error 인자는 발생한 error 메세지를 담고 있습니다.

 

- await ctx.send('Wrong command: {}\nUser /bot? if you need command explanation.'.format(str(error)))

roll_error 함수가 실행되면 위 내용이 실행됩니다.

error 인자에 담긴 error메세지를 담아 discord channel에 출력해주고 command를 잘 모르겠으면 /bot? 명령어를 입력하라는 내용까지 추가해줍니다.

 

 

이대로 코드를 다시 실행시킨 후 discord channel에 잘못된 command를 입력해봅시다.

그러면 위 이미지처럼 잘못된 command를 입력함으로써 발생한 에러메세지와 /bot? 명령어를 이용해서 command 설명을 보라는 메세지까지 잘 출력되는 것을 알 수 있습니다.

 

 

 

- 참고자료

https://discordpy.readthedocs.io/en/latest/ext/commands/api.html#context

https://tutorial.vcokltfre.dev/

 

 

 

 

 

 

728x90
반응형
Comments