5
2

More than 1 year has passed since last update.

【3日目】discord.jsでスラッシュコマンドを作ってみよう

Posted at

はじめに

こんにちは、なりかくんと申します。
今回は、discord.jsを使ってスラッシュコマンドの登録からコマンド処理の作成までやってみようと思います。

スラッシュコマンドの注意

まずスラッシュコマンドは、サーバーにapplications.commandsのスコープを承認される必要があります。サーバーにBotを追加するときに、Discord Developer PortalのOAuth2タブからURLの作成を行うと思います。
そこで、SCOPEを選択する画面を見覚えがありませんか?そこにapplications.commandsスコープがあるのですが、これを有効にしないと追加したアプリケーションでスラッシュコマンドが表示されません。
image.png

なお、もし既にサーバーに追加済みの場合でもapplications.commandsだけのリンクを作って再度認証しなおせば再導入無しで承認することが可能です。(botスコープがあっても問題ありません。)

ギルドコマンドとグローバルコマンド

まず、Discord Botにはギルドコマンドとグローバルコマンドの2種類があります。
ギルドコマンドは、名前の通りサーバーごとに設定できるコマンドですぐに反映されるので開発にはギルドコマンドを利用することが一般的です。
そして、グローバルコマンドは想像通り、導入されているサーバー(applications.commandsの有効が必要)全体に反映されるコマンドです。反映までに数時間かかり1日あたりの作成・更新に制限があります。

また、開発用と公開用でBotを分けておくとギルドコマンドとグローバルコマンドのコマンドの重複を防ぐことが出来ます。

コマンドファイル構造

今回は、コマンドごとにファイルを分けて作っていきます。ファイル構造としては以下のようになります。commandsフォルダーを新しく作りました。この中にコマンドを追加していきます。

discord-bot/
├── node_modules
├── commands
├── config.json
├── index.js
├── package-lock.json
└── package.json

コンフィグファイル

2日目は、コードに直接Tokenを書きましたがconfig.jsonを作って管理をしやすくします。
後ほど使うのでclientIdguildIdもついでに追加しておきましょう。clientIdは、BotのIdです。guildIdは、開発に使うサーバーIdです。

{
	"token": "your-token",
	"clientId": "your-clientId",
	"guildId": "your-guildId"
}

コマンドファイル

今回は、Pingコマンドを作ります。これは、コマンドが実行されたらPong!と返すコマンドです。
commandsの中にping.jsというファイルを作ります。

commands/ping.js
const { SlashCommandBuilder } = require('discord.js');

module.exports = {
	data: new SlashCommandBuilder()
		.setName('ping')
		.setDescription('Pongを返します'),
	async execute(interaction) {
		await interaction.reply('Pong!');
	},
};

SlashCommandBuilderというスラッシュコマンドを作る際には非常に便利なものが用意されていますのでそれを利用します。.setName()の内容がコマンド名になります。

ギルドコマンドの登録

では、今回はギルドコマンドの登録を行っていきます。 deploy-commands.jsというファイルを作ります。これは、Discord APIにコマンド登録を行う用のプログラムです。

deply-commands.js
const { REST, Routes } = require('discord.js');
const { clientId, guildId, token } = require('./config.json');
const fs = require('node:fs');

const commands = [];
const commandFiles = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));

for (const file of commandFiles) {
	const command = require(`./commands/${file}`);
	commands.push(command.data.toJSON());
}

const rest = new REST({ version: '10' }).setToken(token);

(async () => {
	try {
		console.log(`${commands.length} 個のアプリケーションコマンドを登録します。`);

		const data = await rest.put(
			Routes.applicationGuildCommands(clientId, guildId),
			{ body: commands },
		);

		console.log(`${data.length} 個のアプリケーションコマンドを登録しました。`);
	} catch (error) {
		console.error(error);
	}
})();

commandsの中にあるデータをDiscord APIに投げているだけの簡単なプログラムです。

これを実行すると現在はコマンドファイルが1つなので以下のようになります。

> node deploy-commands.js
1 個のアプリケーションコマンドを登録します。
1 個のアプリケーションコマンドを登録しました。

確認してみると、しっかり登録が出来ています。
image.png

グローバルコマンドにする

先ほどはギルドコマンドで登録しましたが、これをグローバルコマンドにしたい場合はapplicationGuildCommandsを少し変えるとグローバルコマンドに変更することが出来ます。

await rest.put(
	Routes.applicationCommands(clientId),
	{ body: commands },
);

コマンド処理をする

では、登録したコマンドを処理するプログラムを書きます。index.jsのコードです。

index.js
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');

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

client.on('ready', () => {
	console.log(`${client.user.tag}でログインしました。`);
});

client.login(token);

client.commands = new Collection();

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);
	if ('data' in command && 'execute' in command) {
		client.commands.set(command.data.name, command);
	} else {
		console.log(`${filePath} に必要な "data" か "execute" がありません。`);
	}
}

client.on('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 });
	}
});

簡単にコードの解説です。
これは、コマンドファイルを読み込んで、Collectionに登録しているプログラムです。

client.commands = new Collection();

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);
	if ('data' in command && 'execute' in command) {
		client.commands.set(command.data.name, command);
	} else {
		console.log(`${filePath} に必要な "data" か "execute" がありません。`);
	}
}

そして、interactionCreateでイベントを受け取ります。
.isChatInputCommand()は、interactionCreateではスラッシュコマンド以外のイベントも受け取るためスラッシュコマンドかどうかを判断するコードです。

if (!interaction.isChatInputCommand()) return;

実際に実行してみると、Pong!としっかり返ってくることが確認できました。
image.png

以上ですっ、最後までお読みいただきありがとうございました。

5
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2