【Discord.js V14】Discord BOTにスラッシュコマンドを実装してみる

皆さん初めまして。Morichanと申します。
下記の※の記述を理解したうえで、記事を読んでください。

※DiscordBot開発初心者の書いた記事です。
（また、プログラミング自体、２年ほどのブランクあり）

環境構築からBOTを動かすまでの流れに関しては、[別の記事](https://qiita.com/Mori-chan/items/add6f63b04f70ff53732)にまとめてあります。

関連記事：
* [【Windows11】Discord BOTを開発するための開発環境を整える。](https://qiita.com/Mori-chan/items/add6f63b04f70ff53732)

## 目次
1. [開発環境](#開発環境)
2. [スラッシュコマンドを実装してみる](#スラッシュコマンドを実装してみる)
3. [関連記事](#関連記事)

## 開発環境
OS (Windows11)
discord.js (14.7.1)
node.js (18.13.0)
npm (8.19.3)



## スラッシュコマンドを実装してみる
まずは、[公式ガイド](https://discordjs.guide/creating-your-bot/slash-commands.html#before-you-continue)に則って、`/ping`と送ったら、`Pong!`とBOTが返信するコマンドを実装してみます。

* ルートディレクトリに`commands`というフォルダを作成。その中に`ping.js`というファイルを作成し、中身を記述。
```
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
	data: new SlashCommandBuilder()
        // コマンドの名前
		.setName('ping')
        // コマンドの説明文
		.setDescription('Pong!と返信。'),
	async execute(interaction) {
        // Pong!と返信
		await interaction.reply('Pong!');
	},
};
```
* ボットが起動時に、コマンドファイルを読み込むための処理とコマンドを判別して実行するための処理を`index.js`に記述。
```
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
const { LISTENER } = require('./config.json');

const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.commands = new Collection();

// commandsフォルダから、.jsで終わるファイルのみを取得
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));

for (const file of commandFiles) {
	const filePath = path.join(commandsPath, file);
	const command = require(filePath);
	// 取得した.jsファイル内の情報から、コマンドと名前をListenner-botに対して設定
	if ('data' in command && 'execute' in command) {
		client1.commands.set(command.data.name, command);
	} else {
		console.log(`[WARNING]  ${filePath} のコマンドには、必要な "data" または "execute" プロパティがありません。`);
	}
}

// コマンドが送られてきた際の処理
client1.on(Events.InteractionCreate, async interaction => {
    // コマンドでなかった場合は処理せずさよなら。
	if (!interaction.isChatInputCommand()) return;

	const command = interaction.client.commands.get(interaction.commandName);

    // 一致するコマンドがなかった場合
	if (!command) {
		console.error(` ${interaction.commandName} というコマンドは存在しません。`);
		return;
	}

	try {
        // コマンドを実行
		await command.execute(interaction);
	} catch (error) {
		console.error(error);
		await interaction.reply({ content: 'コマンドを実行中にエラーが発生しました。', ephemeral: true });
	}
});

client.once(Events.ClientReady, c => {
    console.log(`Ready! Logged in as ${c.user.tag}`);
});

client.login(LISTENER.TOKEN);
```

* 作成したスラッシュコマンドをBOTに登録。
ルートディレクトリに`deploy-commands.js`を作成、中身を記述。
```
const { REST, Routes } = require('discord.js');
const { LISTENER, GUILD_ID } = require('./config.json');
const fs = require('node:fs');

const commands = [];
// Grab all the command files from the commands directory you created earlier
// commandsフォルダから、.jsで終わるファイルのみを取得
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));

// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
for (const file of commandFiles) {
	const command = require(`./commands/${file}`);
	commands.push(command.data.toJSON());
}

// Construct and prepare an instance of the REST module
const rest = new REST({ version: '10' }).setToken(LISTENER.TOKEN);

// and deploy your commands!
(async () => {
	try {
		console.log(`Started refreshing ${commands.length} application (/) commands.`);

		// The put method is used to fully refresh all commands in the guild with the current set
		const data = await rest.put(
			Routes.applicationGuildCommands(LISTENER.CLIENT_ID, GUILD_ID),
			{ body: commands },
		);

		console.log(`Successfully reloaded ${data.length} application (/) commands.`);
	} catch (error) {
		// And of course, make sure you catch and log any errors!
		console.error(error);
	}
})();
```

* VSCodeでターミナルを開き、`node deploy-commands.js`を実行。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3109638/a12db132-dd88-c12f-766a-4a2397ef5f65.png)
* BOTを招待したサーバーへ行き、チャット欄で`/`と入力すると、`/ping`が予測欄に出てくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3109638/0d1588c9-3a13-64e8-3dbf-4305cb4c3da3.png)

* もう一度VSCordへ行き、ターミナルを開いて、`node index.js`を実行。

* Discordサーバーへ行き、チャット欄で`/ping`と入力、送信すると、`Pong!`と返ってくる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3109638/6e5b301f-cfa2-a163-9ad6-1f27ad39e12b.png)

## 関連記事
* [【Windows11】Discord BOTを開発するための開発環境を整える。](https://qiita.com/Mori-chan/items/add6f63b04f70ff53732)
* 【Discord.js V14】Discord BOTにスラッシュコマンドを実装してみる
* [【Discord.js V14】Discord BOTをVCに参加させてみる](https://qiita.com/Mori-chan/items/2df4ad15adc79cf2a167)
* [【Discord.js V14】２つのBOTを別々のVCに参加させてみる](https://qiita.com/Mori-chan/items/7d598933753d0abe1b91)
* [【Discord.js V14】スラッシュコマンドのオプションで、既に選択済みの選択肢を２つ目の引数のリストから消す。](https://qiita.com/Mori-chan/items/89ad3245443f1e8ace07)
* [【Discord.js V14】Discord BOTにVCに音楽を流す機能を実装してみる](https://qiita.com/Mori-chan/items/84622c733dc43c419dc5)
* [【Discord.js V14】Discord BOTにVCから１人分の音声を取得して、録音する機能を実装してみる](https://qiita.com/Mori-chan/items/8fcb1770c4d0020a8284)
* [【Discord.js V14】VC１からVC２に音声をStreaming（中継）するBOTを作成する。](https://qiita.com/Mori-chan/items/e9ced87c0d68a9eb2a9a)
* [【Discord.js V14】取得するVCの音声をロールによって判別する](https://qiita.com/Mori-chan/items/41cd914bb9a93d930bff)