はじめに
こんにちは、なりかくんと申します。
今回は、discord.jsを使ってスラッシュコマンドの登録からコマンド処理の作成までやってみようと思います。
スラッシュコマンドの注意
まずスラッシュコマンドは、サーバーにapplications.commands
のスコープを承認される必要があります。サーバーにBotを追加するときに、Discord Developer PortalのOAuth2タブからURLの作成を行うと思います。
そこで、SCOPEを選択する画面を見覚えがありませんか?そこにapplications.commands
スコープがあるのですが、これを有効にしないと追加したアプリケーションでスラッシュコマンドが表示されません。
なお、もし既にサーバーに追加済みの場合でも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
を作って管理をしやすくします。
後ほど使うのでclientId
とguildId
もついでに追加しておきましょう。clientId
は、BotのIdです。guildId
は、開発に使うサーバーIdです。
{
"token": "your-token",
"clientId": "your-clientId",
"guildId": "your-guildId"
}
コマンドファイル
今回は、Pingコマンドを作ります。これは、コマンドが実行されたらPong!
と返すコマンドです。
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にコマンド登録を行う用のプログラムです。
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 個のアプリケーションコマンドを登録しました。
グローバルコマンドにする
先ほどはギルドコマンドで登録しましたが、これをグローバルコマンドにしたい場合はapplicationGuildCommands
を少し変えるとグローバルコマンドに変更することが出来ます。
await rest.put(
Routes.applicationCommands(clientId),
{ body: commands },
);
コマンド処理をする
では、登録したコマンドを処理するプログラムを書きます。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!
としっかり返ってくることが確認できました。
以上ですっ、最後までお読みいただきありがとうございました。