본문 바로가기

꿀팁!

discord.ext.command 공부

Commands

@bot.command()
async def foo(ctx, arg):
    await ctx.send(arg)

$foo abc를 통해 명령어 호출이 가능한 것처럼 기본적으로 데코레이터 아래에 있는 함수이름이 명령어가 되어 사용된다.

 

from discord.ext import commands

bot = commands.Bot(command_prefix='$')

@bot.command()
async def test(ctx):
    pass

# or:

@commands.command()
async def test(ctx):
    pass

bot.add_command(test)

명령어 호출의 가장 기본적인 형태로 command_prefix='$'를 통해 어떤 문장으로 시작할때 명령어로 인식할것인지 설정이 가능하다. ex) $foo abc

위와 아래 모두 사용이 가능하지만 @bot.command()를 사용할경우 따로 추가해줄 필요가 없어 편리하다.

@bot.command(name='list')
async def _list(ctx, arg):
    pass

명령어를 함수명과 같지 않게 하고싶을때는 bot.command의 name값을 문자열로 설정해주어 명령어를 지정할수있다.

 

Parameters

@bot.command()
async def test(ctx, arg):
    await ctx.send(arg)

기본적으로 위치매개변수 방식으로 명령어에는 반드시 하나이상의 인자가 있으며 소스처럼 두번째 변수 arg로 받아 사용할수있는것을 알수있다. (인자의 갯수는 자유롭게 설정할수있다.) 인자값에서 공백이 들어간 문자열을 주고싶을때는 "문자열"을 이용해 주면된다.

 

@bot.command()
async def test(ctx, *args):
    await ctx.send('{} arguments: {}'.format(len(args), ', '.join(args)))

인자갯수가 명확하지 않은 것은 위의 소스처럼하면된다.

 

Converters

@bot.command()
async def add(ctx, a: int, b: int):
    await ctx.send(a + b)

다음처럼 명령어에 :int 를 써줌으로써 입력값을 int형으로 바꿔줄수있고

def to_upper(argument):
    return argument.upper()

@bot.command()
async def up(ctx, *, content: to_upper):
    await ctx.send(content)

python3의 기능으로 함수 주석을 이용한 것이다. content: to_upper처럼 content라는 변수에 to_upper함수를 지정하여 대문자로 바꿔주는 루틴을 수행한다.

 

if lowered in ('yes', 'y', 'true', 't', '1', 'enable', 'on'):
    return True
elif lowered in ('no', 'n', 'false', 'f', '0', 'disable', 'off'):
    return False

bool 타입은 기존과 다르게 처리된다.

 

Advanced Converter

import random

class Slapper(commands.Converter):
    async def convert(self, ctx, argument):
        to_slap = random.choice(ctx.guild.members)
        return '{0.author} slapped {1} because *{2}*'.format(ctx, to_slap, argument)

@bot.command()
async def slap(ctx, *, reason: Slapper):
    await ctx.send(reason)

확장된 변환기로 사용자가 만들 수 있다. 만들때는 Converter.convert()을 이용한다.

다음 예제는 멤버중 한명을 send 입력값과 함께 뽑아준다.

@bot.command()
async def clean(ctx, *, content: commands.clean_content):
    await ctx.send(content)

# or for fine-tuning

@bot.command()
async def clean(ctx, *, content: commands.clean_content(use_nicknames=False)):
    await ctx.send(content)

clean_content()을 이용해 일부 상태값을 조정할수있다.

class MemberRoles(commands.MemberConverter):
    async def convert(self, ctx, argument):
        member = await super().convert(ctx, argument)
        return [role.name for role in member.roles[1:]] # Remove everyone role!

@bot.command()
async def roles(ctx, *, member: MemberRoles):
    """Tells you a member's roles."""
    await ctx.send('I see the following roles: ' + ', '.join(member))

디스코드 클래스를 이용해 컨버터 제작을 할 수 있다.

 

Special Converters

typing.Union 은 복수의 컨버터를 넣을 수 있게한다. 왼쪽에서 오른쪽순으로 작동하며 모두 실패시BadUnionArgument에러가 발생한다.

import typing

@bot.command()
async def union(ctx, what: typing.Union[discord.TextChannel, discord.Member]):
    await ctx.send(what)

 typing.Optional은 역참조 동작을 수행한다. 컨버터가 지정된 타입으로 못바꾸면 NONE대신 지정한 기본값으로 매개변수에 전달이 된다.

import typing

@bot.command()
async def bottles(ctx, amount: typing.Optional[int] = 99, *, liquid="beer"):
    await ctx.send('{} bottles of {} on the wall!'.format(amount, liquid))

예제로 입력값으로 water을 줬지만 int형으로 못바꾸기에 기본값인 99를 주고 뒤의 liquid값을 water로 바꾼모습

 

Greedy컨버터는 더이상 타입변환을 할 수 없을때까지 계속변환한다.

@bot.command()
async def slap(ctx, members: commands.Greedy[discord.Member], *, reason='no reason'):
    slapped = ", ".join(x.name for x in members)
    await ctx.send('{} just got slapped for {}'.format(slapped, reason))

 

오류처리

Command.error()를 통해 오류를 처리한다.

@bot.command()
async def info(ctx, *, member: discord.Member):
    """Tells you some info about the member."""
    fmt = '{0} joined on {0.joined_at} and has {1} roles.'
    await ctx.send(fmt.format(member, len(member.roles)))

@info.error
async def info_error(ctx, error):
    if isinstance(error, commands.BadArgument):
        await ctx.send('I could not find that member...')

검사

async def is_owner(ctx):
    return ctx.author.id == 316026178463072268

@bot.command(name='eval')
@commands.check(is_owner)
async def _eval(ctx, *, code):
    """A bad example of an eval command"""
    await ctx.send(eval(code))

특정 사용자만 명령어를 사용하게 하기 위해 검사를 해야하는데 예제의 check데코레이터를 통해 하면서 True False로 리턴하면서 체크할수있다.

def is_owner():
    async def predicate(ctx):
        return ctx.author.id == 316026178463072268
    return commands.check(predicate)

@bot.command(name='eval')
@is_owner()
async def _eval(ctx, *, code):
    """A bad example of an eval command"""
    await ctx.send(eval(code))

 is_owner() 라이브러리를 이용해 할수도있다.

 

def is_in_guild(guild_id):
    async def predicate(ctx):
        return ctx.guild and ctx.guild.id == guild_id
    return commands.check(predicate)

@bot.command()
@commands.is_owner()
@is_in_guild(41771983423143937)
async def secretguilddata(ctx):
    """super secret stuff"""
    await ctx.send('secret stuff')

체크를 중첩으로 할수도있다.

 

@bot.check
async def globally_block_dms(ctx):
    return ctx.guild is not None

만약 특정 명령이 아닌 모든 명령에 적용하고 싶다면 Bot.check()를 이용하면 된다.