Qiita初投稿は、Bot作成の中で詰まった、「サブコマンドを指定しないときのみ実行する方法」について備忘録としてメモを残したいと思います。
この記事ではCommandsというボットコマンドのフレームワークを使用しています。また、機能を細かく分けて拡張性を上げるためにCogという機能も使用しています。command_prefixは統一して「!」です。
#やりたかったこと
Botにちょっとした便利機能としてrollという機能を実装しようと思ったときに、a~bの範囲の中からn個の整数をランダムで選んでくれるnumと、「キーワード1」、「キーワード2」...「キーワードa」の中からn個のキーワードをランダムで選んでくれるkeyという2つの機能を実装しよう!というアイデアが降ってきて早速コードを打って動かしてみたのですが、見事に失敗してしまいました。
#失敗コード
import discord
from discord.ext import commands
import random
class Roll(commands.Cog):
def __init__(self,bot):
self.bot = bot
@commands.group()
async def roll(self,ctx):
embed = discord.Embed(color=0x0080ff)
embed.add_field(name='roll num a b n',value='a~bの範囲の値をn個表示します',inline=False)
embed.add_field(name='roll key n 候補1 候補2 … 候補a', value='a個の候補の中からn個選びます',inline=False)
await ctx.send(embed=embed)
@roll.command()
async def num(self,ctx,a,b,n):
a = int(a)
b = int(b)
n = int(n)
embed = discord.Embed(color=0x0080ff)
if a>b:
a,b = b,a
if b - a < n:
embed.add_field(name='ERROR',value='個数が多すぎます')
else:
li = [i for i in range(a,b+1)]
res = random.sample(li,n)
embed.add_field(name="roll結果", value=sorted(res), inline=False)
await ctx.send(embed=embed)
@roll.command()
async def key(self,ctx,n,*key):
n = int(n)
embed = discord.Embed(color=0x0080ff)
if len(key)<n:
embed.add_field(name='ERROR',value='個数が多すぎます')
else:
res = ' '.join(random.sample(key,n))
embed.add_field(name="roll結果", value=res, inline=False)
await ctx.send(embed=embed)
def setup(bot):
bot.add_cog(Roll(bot))
#コマンド説明
!roll・・・rollコマンドについての説明
!roll num a b n・・・a~bの範囲からn個の整数をランダムで選出
!roll key 候補1 候補2 … 候補a・・・a個のキーワード候補の中からn個をランダムで選出
#問題点
パット見これで「!roll num a b n」や「!roll key 候補1 候補2 … 候補a」が実行できるように見えますが、実は「!roll num a b n」コマンドを実行すると**!rollコマンドと!roll num a b nコマンドが同時に実行されるため、ランダムで選出してもらう度にわざわざrollコマンドについての説明が出てしまうのです。これが今回の問題です。
#解決方法
解決方法はとても単純でした。
rollを定義する時の@commands.group()にinvoke_without_command=True**を与えてあげるだけでした
#成功コード
import discord
from discord.ext import commands
import random
class Roll(commands.Cog):
def __init__(self,bot):
self.bot = bot
@commands.group(invoke_without_command=True)
async def roll(self,ctx):
embed = discord.Embed(color=0x0080ff)
embed.add_field(name='roll num a b n',value='a~bの範囲の値をn個表示します',inline=False)
embed.add_field(name='roll key n 候補1 候補2 … 候補a', value='a個の候補の中からn個選びます',inline=False)
await ctx.send(embed=embed)
@roll.command()
async def num(self,ctx,a,b,n):
a = int(a)
b = int(b)
n = int(n)
embed = discord.Embed(color=0x0080ff)
if a>b:
a,b = b,a
if b - a < n:
embed.add_field(name='ERROR',value='個数が多すぎます')
else:
li = [i for i in range(a,b+1)]
res = random.sample(li,n)
embed.add_field(name="roll結果", value=sorted(res), inline=False)
await ctx.send(embed=embed)
@roll.command()
async def key(self,ctx,n,*key):
n = int(n)
embed = discord.Embed(color=0x0080ff)
if len(key)<n:
embed.add_field(name='ERROR',value='個数が多すぎます')
else:
res = ' '.join(random.sample(key,a))
embed.add_field(name="roll結果", value=res, inline=False)
await ctx.send(embed=embed)
def setup(bot):
bot.add_cog(Roll(bot))
これで、!rollを実行したときはrollの説明が出て、!roll num a b nを実行したときはnumの機能のみが実行されるようになりました!
因みに今回のrollコマンドではあまり意味はないですが、invoke_without_commandをTrueにするとrollコマンドの後にa b nのような引数を指定して実行できるようです。
#終わりに
今回、この問題を解決するにあたってDiscord.pyのJP鯖の方にお力を貸していただきました!本当にありがとうございました。(鯖のリンク等を載せていいかわからないのでこのような形でごめんなさい🙇)
また、この記事で間違っている点やこうしたほうがいい!というのがありましたらコメントしていただけると助かります!