はじめに
この記事はDiscordBotアドベントカレンダー2020の19日目の記事です。
こんにちは。この記事では、つい先日オープンベータになった、スラッシュコマンドを試してみたいと思います。
スラッシュコマンドは、公式にサポートされたコマンド機能です。組み込みのコマンドもいくつかあり、例えば /tableflip おりゃー
と送信すると、おりゃー (╯°□°)╯︵ ┻━┻と変換してくれるコマンドなどがあります。
スラッシュコマンドは利用するユーザーにやさしいのが特徴です。/を打てば、コマンドの名前と機能の説明が表示されるだけでなく、コマンドをTabで補完したり、引数をユーザー型やチャンネル型などに限定することも出来ます。
なお、この機能はベータ版であり、今後動かなくなる可能性があることに注意してください。
コマンドの登録
Botの権限を変更する
まず、コマンドを追加するためにBotに権限が必要なので、Discord Developer Portal — My Applicationsから新しくサーバーへの招待リンクを作り直します。
アプリケーションを選択👉OAuth2👉OAuth2 URL Generator
からapplications.commands
にチェックを入れ、生成されたURLを踏んで、Botのサーバー権限を上書きします。
コマンドを追加する
いよいよ、コマンドを追加していきます。コマンドの追加は、以下のURLにPOSTでリクエストすることで可能です。
https://discord.com/api/v8/applications/{application.id}/commands
公式チュートリアルを少し弄ったものをリクエストします。例はPythonです。
import requests
url = "https://discord.com/api/v8/applications/<APPLICATION_ID>/commands"
json = {
"name": "blep",
"description": "Send a random adorable animal photo",
"options": [
{
"name": "animal",
"description": "The type of animal",
"type": 3,
"required": True,
"choices": [
{
"name": "Dog",
"value": "animal_dog"
},
{
"name": "Cat",
"value": "animal_cat"
},
{
"name": "Penguin",
"value": "animal_penguin"
}
]
},
{
"name": "only_smol",
"description": "Whether to show only baby animals",
"type": 5,
"required": False
}
]
}
# For authorization, you can use either your bot token
headers = {
"Authorization": "Bot <BOT_TOKEN>"
}
r = requests.post(url, headers=headers, json=json)
print(r.json())
レスポンスに登録したコマンドの情報に加えて、IDなどが帰ってきていれば登録成功です。
ここまで上手くいっていれば/blep
でコマンドが表示されるはずです。ただし、この時点では送信しても特に何も起こりません。
コマンドに返信する
方法は二つあります。一つはGateway EventのInteractionEventをキャッチする方法。もう一つはwebhookを用意する方法です。今回は一つ目で行きます。
新しい機能なのでどのフレームワークにも当然実装されていません。ということで、生のDiscord Gatewayを触る...のは流石に面倒なのでdiscordpyを拡張しながら実装していきます。
コマンドをキャッチする
from discord.ext import commands
from pprint import pprint
TOKEN = "<BOT TOKEN>"
class MyBot(commands.Bot):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.add_listener(self.on_socket_response)
async def on_socket_response(self,msg):
if msg["t"] != "INTERACTION_CREATE":
return
pprint(msg)
if __name__ == '__main__':
bot = MyBot(command_prefix='!',
description='slash test')
bot.run(TOKEN)
これで、スラッシュコマンドのEventにのみ反応し、Eventの内容は以下のような辞書で手に入ります。
{'d': {'channel_id': '<CHANNEL ID>',
'data': {'id': '<COMMAND ID>',
'name': 'blep',
'options': [{'name': 'animal', 'value': 'animal_dog'},
{'name': 'only_smol', 'value': True}]},
'guild_id': '<GUILD ID>',
'id': '<COMMAND INTERACTION ID>',
'member': {'deaf': False,
'is_pending': False,
'joined_at': '2020-01-13T14:08:23.570000+00:00',
'mute': False,
'nick': None,
'pending': False,
'permissions': '11111111',
'premium_since': None,
'roles': [],
'user': {'avatar': '1111111111111111111111',
'discriminator': '1111',
'id': '11111111111111',
'public_flags': 111,
'username': 'Pishiko'}},
'token': '<COMMAND INTERACTION TOKEN>',
'type': 2,
'version': 1},
'op': 0,
's': 3,
't': 'INTERACTION_CREATE'}
コマンドに返信する
これで、返信するのに必要なコマンドのアクション(=Interaction)のIDとトークンが分かったので、以下のURLにPOSTでリクエストします。
https://discord.com/api/v8/interactions/{interaction.id}/{interaction.token}/callback
discordpyでリクエストするにはaiohttpモジュールを利用します。以下のような関数を用意して返信します。
async def reply(id,token,content):
url = "https://discord.com/api/v8/interactions/{0}/{1}/callback".format(
id, token)
json = {
"type":4,
"data":{
"content":content
}
}
async with aiohttp.ClientSession() as s:
async with s.post(url,json=json) as r:
if 200 <= r.status < 300:
return
最終的に以下のようなコードになりました。
from discord.ext import commands
from pprint import pprint
import aiohttp
TOKEN = "<BOT TOKEN>"
class MyBot(commands.Bot):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.add_listener(self.on_socket_response)
async def on_socket_response(self,msg):
if msg["t"] != "INTERACTION_CREATE":
return
await reply(msg["d"]["id"], msg["d"]["token"],str(msg["d"]["data"]["options"]))
async def reply(id, token, content):
url = "https://discord.com/api/v8/interactions/{0}/{1}/callback".format(
id, token)
json = {
"type": 4,
"data": {
"content": content
}
}
async with aiohttp.ClientSession() as s:
async with s.post(url, json=json) as r:
if 200 <= r.status < 300:
return
if __name__ == '__main__':
bot = MyBot(command_prefix='!',
description='slash test')
bot.run(TOKEN)
うまくいけば、このように表示されるはずです。
おわりに
この他にも、サブコマンド、ユーザーやチャンネルの指定、返信の編集や表示の変更など、出来ることはまだあるので、以下を参考にしながらいろいろ遊んでみてください。