실전! 초보를 위한 디스코드 봇 개발 with 파이썬 (4) - 명령어 체크와 명령어 오류

2020. 5. 10. 17:15디스코드 봇 만들기

지난 시간에 기본 명령어를 만들어 보았고 봇 접두사도 정해 보았습니다. 이번 시간에는 '명령어 체크' 기능과 '명령어 파라미터'를 사용해 보려고 합니다.

 

1. 명령어 체크

명령어 체크 기능은 말 그대로 명령어를 사용할 수 있는 조건을 거는 것입니다. 봇 주인만 사용 가능한 명령어, 길드에서만 사용 가능한 명령어 등을 만들 때 쓰입니다.

 

1) 봇 주인만 사용 가능하게 해보자!

@client.command(name='방해금지')
async def dnd(ctx):
    await client.change_presence(status=discord.Status.dnd)
    await ctx.send('봇 상태를 방해금지로 변경했습니다.')

@client.command(name='온라인')
async def online(ctx):
    await client.change_presence(status=discord.Status.online)
    await ctx.send('봇 상태를 온라인으로 변경했습니다.')

위와 같은 명령어 2개를 추가했습니다. '방해금지'라고 입력하면 봇의 상태가 방해금지로, '온라인'을 입력하면 봇의 상태가 온라인으로 변하는 명령입니다. 이때 change_presence라는 명령어 사용하게 되는데, 봇의 상태를 바꾸는 명령어입니다. 이는 나중에 자세히 살펴보겠습니다.

 

하지만 이렇게 하면 아래와 같이 주인이 아닌 사용자도 해당 명령어를 사용할 수 있습니다.

그렇다면 봇 주인만 특정 명령어를 사용할 수 있게 해 봅시다.

 


방법은 아주 간단합니다. 그냥 다음과 같이 명령어에 commands.is_owner()를 데코레이터로 붙여주면 됩니다.

@client.command(name='방해금지')
@commands.is_owner()
async def dnd(ctx):
    await client.change_presence(status=discord.Status.dnd)
    await ctx.send('봇 상태를 방해금지로 변경했습니다.')

@client.command(name='온라인')
@commands.is_owner()
async def online(ctx):
    await client.change_presence(status=discord.Status.online)
    await ctx.send('봇 상태를 온라인으로 변경했습니다.')

이제 봇을 재시작하면 봇 주인만이 해당 명령어를 사용할 수 있게 됩니다.

 

2. 명령어 오류

만약 봇 주인이 아닌 유저가 주인 전용 명령을 입력했다면, 아래와 같이 NotOwner에러가 발생하게 됩니다. 이와 같이 명령어 실행 과정에서 발생한 오류를 명령어 오류라고 합니다. 이때, 주인만 사용 가능한 명령이라고 메시지를 보내 봅시다.

Ignoring exception in command 온라인:
Traceback (most recent call last):
  File "C:\Users\arpa1\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\ext\commands\bot.py", line 892, in invoke
    await ctx.command.invoke(ctx)
  File "C:\Users\arpa1\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\ext\commands\core.py", line 790, in invoke
    await self.prepare(ctx)
  File "C:\Users\arpa1\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\ext\commands\core.py", line 743, in prepare
    if not await self.can_run(ctx):
  File "C:\Users\arpa1\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\ext\commands\core.py", line 1011, in can_run
    return await discord.utils.async_all(predicate(ctx) for predicate in predicates)
  File "C:\Users\arpa1\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\utils.py", line 326, in async_all
    elem = await elem
  File "C:\Users\arpa1\AppData\Local\Programs\Python\Python37\lib\site-packages\discord\ext\commands\core.py", line 1803, in predicate
    raise NotOwner('You do not own this bot.')
discord.ext.commands.errors.NotOwner: You do not own this bot.

봇 코드 최상단에 import traceback 을 추가해준 후, 아래와 같은 코루틴을 봇 코드에 추가해줍니다. 코루틴 이름인 on_command_error 은 변경하면 안 됩니다.

@client.event
async def on_command_error(ctx, error):
    tb = traceback.format_exception(type(error), error, error.__traceback__)
    err = [line.rstrip() for line in tb]
    errstr = '\n'.join(err)
    if isinstance(error, commands.NotOwner):
        await ctx.send('봇 주인만 사용 가능한 명령어입니다')
    else:
        print(errstr)

 

그러면 전체 봇 코드는 다음과 같게 됩니다.

import discord
import traceback
from discord.ext import commands

client = commands.Bot(command_prefix='아케봇 ')

@client.command(aliases=['안녕', '안녕하세요', 'ㅎㅇ'])
async def hello(ctx):
    await ctx.send('안녕하세요')

@client.command(name='방해금지')
@commands.is_owner()
async def dnd(ctx):
    await client.change_presence(status=discord.Status.dnd)
    await ctx.send('봇 상태를 방해금지로 변경했습니다.')

@client.command(name='온라인')
@commands.is_owner()
async def online(ctx):
    await client.change_presence(status=discord.Status.online)
    await ctx.send('봇 상태를 온라인으로 변경했습니다.')

@client.event
async def on_command_error(ctx, error):
    tb = traceback.format_exception(type(error), error, error.__traceback__)
    err = [line.rstrip() for line in tb]
    errstr = '\n'.join(err)
    if isinstance(error, commands.NotOwner):
        await ctx.send('봇 주인만 사용 가능한 명령어입니다')
    else:
        print(errstr)

client.run('봇 토큰')

 

여기서, 방금 추가한 on_command_error 코루틴은 명령어 실행 중 오류가 났을 때 실행됩니다. 이 코드를 자세히 들여다보겠습니다.

async def on_command_error(ctx, error) 명령어 처리 중 오류가 발생했을 때 실행되는 코루틴.

tb = traceback.format_exception(type(error), error, error.__traceback__)
err = [line.rstrip() for line in tb]
errstr = '\n'.join(err)

오류 내용(오류가 났을 때 출력되는 메시지)을 errstr 변수에 담습니다
if isinstance(error, commands.NotOwner): 만약 발생한 오류가 commands.NotOwner오류인가?
await ctx.send('봇 주인만 사용 가능한 명령어입니다') '봇 주인만 사용 가능한 명령어입니다' 라고 같은 채널에 전송
else:
    print(errstr)
만약 발생한 오류가 commands.NotOwner오류가 아니라면 오류 내용을 그대로 출력합니다.

오류 내용을 errstr변수에 저장해서 수동으로 출력하는 이유는, on_command_error 코루틴을 사용하게 되면 더 이상 명령어 오류 메시지가 출력되지 않기 때문입니다. 따라서, 처리할 오류 외의 오류가 발생했다면, 오류 메시지를 출력해야 오류의 원인을 찾고 고치기가 쉽습니다.

 

이제, 봇을 켜봅시다.

성공적으로 작동하는 것을 볼 수 있습니다!