generated at
スラッシュコマンドを使ってみよう
スラッシュコマンドをクライアントに表示するためにはDiscordへコマンドを登録する必要がある。
また、スラッシュコマンドを表示/使用するためにはアプリケーションに対してサーバーへの導入時に applications.commands スコープが与えられていなければならない。
bot スコープはInteractionsに付属してくるトークンのみで呼び出せないAPIなどを呼び出す場合、Gatewayイベントを使用する場合などに依然として必要である。
導入URLの例をいくらか示す。
2021/3/26以前にボットがサーバーに導入されており、以後Kick、Banされていない場合、Botは applications.commands スコープを持っていると考えて差し支えない。
言い換えると Guild#joinedAt が2021/3/26以前ならば、スラッシュコマンドを登録さえすればサーバーで使用できるということである。
> Scope migration: Applications with the bot scope were granted the applications.commands scope. Starting today, you should add applications.commands to your invite link if you plan to use slash commands.
そうでない場合は利用者に applications.commands スコープを付与して貰う必要がある

以下がスラッシュコマンドの登録に使う簡単なCLIアプリケーションである。
register.js
const { Client, ClientApplication } = require("discord.js"); /** * * @param {Client} client * @param {import("discord.js").ApplicationCommandData[]} commands * @param {import("discord.js").Snowflake} guildID * @returns {Promise<import("@discordjs/collection").Collection<string,import("discord.js").ApplicationCommand>>} */ async function register(client, commands, guildID) { if (guildID == null) { return client.application.commands.set(commands); } return client.application.commands.set(commands, guildID); } const ping = { name: "ping", description: "pong!", }; const hello = { name: "hello", description: "botがあなたに挨拶します。", options: [ { type: "STRING", name: "language", description: "どの言語で挨拶するか指定します。", required: true, choices: [ { name: "English", value: "en" }, { name: "Japanese", value: "ja" } ], } ] }; const commands = [ping, hello]; const client = new Client({ intents: 0, }); client.token = process.env.DISCORD_TOKEN; async function main() { client.application = new ClientApplication(client, {}); await client.application.fetch(); await register(client, commands, process.argv[2]); console.log("registration succeed!"); } main().catch(err=>console.error(err));

次のような手順でスラッシュコマンドを登録する。
上記のコードを適当な名前で保存する。
ここでは以下のように保存したものとして進める。
cmd
D:. │ package.json │ register.js │ yarn.lock
Client.loginの引数にトークンを書きたくないの方法に従ってトークンを環境変数に設定する。
node register.js <your guild id> を実行する。
うまく行けば registration succeed! とコンソールに出るだろう。
トラブルシューティング
Error: Cannot find module 'D:\djs-v13\regist.js'
jsファイルが見つからない。
D:\djs-v13\regist.js にファイルは存在するか?
カレントディレクトリは正しい?
ファイル名を間違えてない?
HTTPError [DiscordjsError]: Request to use token, but token was unavailable to the client.
Discord.jsのクライアントにトークンがわたされていない。
環境変数にトークンを設定した?
環境変数の名前を間違えていない?
DiscordAPIError: 401: Unauthorized のうち path: "/oauth2/applications/@me" であるもの
誤ったトークンを入力していない?
DiscordAPIError: Missing Access のうち code:50001 であり path:'/guilds/<your guild id>?with_counts=true' であるもの
サーバーにbotは導入されている?
DiscordAPIError: Missing Access のうち code:50001 であり path: '/applications/<your application id>/guilds/<your guild id>/commands であるもの
applications.commands つきでアプリケーションを導入した?
その後Discordクライアントを再起動する。
PCのクライアントを使用している場合はCtrl+Rでもよい。
再起動しなくてもロードされます(WindowsStable, 2021.12.28現在)anmoti
/と入力欄に打って登録されているか確認してみよう。
以下の画像のように登録されたコマンドが確認できるはずだ。
どちらかのコマンドを実行してみよう。
現時点では失敗するはずだ。
次はbotが応答を返せるようにしていこう。

解説
register 関数
guildID null または undefined である場合グローバルコマンドを登録し、関数はその返り値を返却する。
グローバルコマンドの登録は client.application.commands.set というメソッドで行っている。
client.application はnullableであるが、ここではそのようなことは起き得ないものとして進める。(mainの実装をみれば明らかになるだろうし、 login (後にコマンドに実際に応答させるときに触れる)後は特別に利用者が代入しなければ、nullになりえない)
guildID としてサーバーidがわたされてきた場合、そのサーバーidを持つGuildを取得し、そのサーバーへコマンドを登録する。
このようにして guildID がわたされた場合はサーバー単位でコマンドを登録し、そうではなくnullishな値が渡された(あるいは何もわたされなかった)場合はグローバルコマンドを登録する関数が定義できた。
ping 変数を見ていこう。
これは /ping スラッシュコマンドの定義である。
つまり、 ApplicationCommandData である。
name
コマンドの名前を指定する。
これは必須である。
discordはコマンド名に以下のような制約を課している。
アプリケーションは同じ名前を持つ2つのグローバルコマンドを持つことはできない
アプリケーションは同じ名前を持つ2つのギルドコマンドを持つことはできない
同じ名前のグローバルコマンドとギルドコマンドを持つことは可能
複数のアプリケーションが同じ名前を持つことは可能
正規表現 ^[\w-]{1,32}$ に一致し、かつlower caseである必要がある。
lower caseというのは小文字という意味である。
つまり、 a から z までの英小文字と、 0 から 9 までの数字、それに加えて - _ で構成された1文字以上32文字以下の文字列であることが求められている。
Registering a Command Discord Developer Portal
description
コマンドの説明を指定する。
これも必須である。
1から100文字である必要がある。
Create Global Application Command Discord Developer Poral
hello 変数も見てみよう。
これは /hello コマンドの定義である。
名前は hello で、コマンドの説明は botがあなたに挨拶します。 である。
options
これはコマンドのオプション(関数の引数とでも思ったほうがわかりやすいかもしれない)を定義する。
順番が保持される。
定義順にUIに現れる。
コマンドは全部で25個のオプションを持つことができる。
ここでは1つだけ引数を定義していてその名前は language である。
[0]
type
引数の型を指定する。
必須。
今回は文字列とした。
ApplicationCommandOptionType Discord Developer Portal
name
オプションの名前
ここでは language
コマンド名と同様の制約が課される。
description
オプションの説明。
1から100文字である必要がある。
required
このオプションが必須であるか。
ここでは、オプションは必須とした。
required 自体を任意で省略した場合は false 、このオプションを指定するかは任意になる。
choices
[idx]
name
選択肢の名前
1文字以上100文字以下である必要がある。
value
選択肢の値。
文字列あるいは数字。
type の型と一致している必要がある。
文字列の場合は100文字以下。
実際に選択肢が選択されたときAPIからはその値が送られてくる。
ApplicationCommandOptionChoice Discord Developer Portal
ApplicationCommandOption Discord Developer Portal
js
const client = new Client({ intents: 0, });
Clientを宣言している。
Gatewayには接続しないので intents は適当に0とした。
js
client.token = process.env.DISCORD_TOKEN;
通常 login でトークンを渡すが、 login は呼び出さないのでここでトークンをClientに渡す。
main 関数。
js
client.application = new ClientApplication(client, {}); await client.application.fetch();
client.application は通常 ready の発生時に初期化される。
しかし、Gatewayに接続しないため ready イベントは発生しない。
なのでこちらで初期化する。
しないと、 set の呼び出しでエラーになる。
READY.js discordjs/discord.js
コマンドの定義の詳細を知りたい場合はスラッシュコマンドの定義で詳細な情報が確認できる。

さて、以下がhelloコマンドとpingコマンドの両方を実装したものである。
index.js
const Discord = require("discord.js"); const commands = { /** * * @param {Discord.CommandInteraction} interaction * @returns */ async ping(interaction) { const now = Date.now(); const msg = [ "pong!", "", `gateway: ${interaction.client.ws.ping}ms`, ]; await interaction.reply({ content: msg.join("\n"), ephemeral: true }); await interaction.editReply([...msg, `往復: ${Date.now() - now}ms`].join("\n")); return; }, /** * * @param {Discord.CommandInteraction} interaction * @returns */ async hello(interaction) { const source = { en(name){ return `Hello, ${name}!` }, ja(name){ return `こんにちは、${name}さん。` } }; const name = interaction.member?.displayName ?? interaction.user.username; const lang = interaction.options.get("language"); return interaction.reply(source[lang.value](name)); } }; async function onInteraction(interaction) { if (!interaction.isCommand()) { return; } return commands[interaction.commandName](interaction); } const client = new Discord.Client({ intents: 0 }); client.on("interactionCreate", interaction => onInteraction(interaction).catch(err => console.error(err))); client.login(process.env.DISCORD_TOKEN).catch(err => { console.error(err); process.exit(-1); });

以下のような手順で実行する。
Client.loginの引数にトークンを書きたくないの方法に従ってトークンを環境変数に設定する。
node index.js を実行する。
入力欄より /ping コマンドを実行する
うまく行けばbotから次のような応答が得られるだろう。
/hello language: en /hello language: ja を実行してみる。

解説
on_interaction 関数
Interaction 型となるような型はいくつかあり、その中でも今回処理したいスラッシュコマンドを表す型は CommandInteraction である。
Interaction#isCommand Interaction CommandInteraction であるかを判定するためのメソッドである。
ちなみに、他には MessageComponentInteraction が定義されており、その配下に ButtonInteraction が定義されている。
Interaction discord.js
ping コマンド
js
const msg = [ "pong!", "", `gateway: ${interaction.client.ws.ping}ms`, ]; await interaction.reply({ content: msg.join("\n"), ephemeral: true });
ephemeral: true は応答を ephemeral なものとするというオプションを指定しているものである。
ephemeralなメッセージは次のような特徴を持つ
送信されてきた人にしか見えない。
スラッシュコマンドへの応答としてのみ送信可能。
閉じたり(Dissmiss messageを押す)、時間が経ったり、Discordを再起動したりした場合には消える。
defer あるいは reply を三秒以内に呼び出さない場合discordによってその呼び出しは失敗したとみなされる。
js
await interaction.editReply([...msg, `往復: ${Date.now() - now}ms`].join("\n"));
CommandInteraction#editReply メソッドによってもとの応答を変更することができる。
ここでは最終行に reply 関数を呼び出し、その返答が帰ってくるまでの時間を追加している。
hello コマンド
js
const lang = interaction.options.get("language");
CommandInteraction#options のCollectionのキーはオプション名となっている。
これを用いて language オプションの値を取得した。
スラッシュコマンドが実行されると interaction イベントが発生する。
ここではIntentsとして、 0 (特別なイベントを受信しない)を指定している

満足に動いていることが確認できたならば、 node register.js を実行してグローバルコマンドを登録/更新しよう。

registerV14.js
const { Client, GatewayIntentBits } = require("discord.js") const client = new Client({ intents: [ GatewayIntentBits.Guilds ] }) client.on("interactionCreate", async (interaction) => { if (interaction.isChatInputCommand()){ const { commandName } = interaction if (commandName === "ping"){ const now = Date.now() const text = `pong!\n\ngateway:${client.ws.ping}ms` await interaction.reply({ content: text, ephemeral: true }) return interaction.editReply(`${text}\n往復:${Date.now() - now}ms`) } else if (commandName === "hello"){ const lang = { ja: (name) => `こんにちは、${name}さん。`, en: (name) => `Hello, ${name}!` } return interaction.reply(lang[interaction.options.getString("language")](interaction.member?.displayName || interaction.user.username)) } } })