【discord.js】v13でbotを作ってみる

初書 ： 2021/08/09
node : 16.6.0
discord.js: v13.0.1

# 前書き

先日、discord.jsがv13をリリースしたので、早速使ってみる。
なお、初書時点ではスレッドやボタンなど、v13で新しく登場した機能の一部は触れていないが、興味はあるので近々追記もしくは別記事で投稿する予定。

# 前提

・botのトークンの取得やサーバーへの導入の準備が終わっていること
・nodejsの基本的な扱いは出来ること。

# インストール

まずはnodeのバージョン確認。v13は最低要件で16.6.0を要求するので、それ以下の場合はアップデートする必要がある。

```zsh
% node --version
v16.6.1
```

もし更新していない場合はnodebrewなどでアップデートする必要がある。

出来れば、次はプロジェクトの作成。今回は単純に`% npm init -y`で作成した。

あとはdiscord.jsをインストールする。

```
% npm install discord.js
```

また、discordのapiを使う時にenvファイルを使うので、dotenvもインストールしておく。

```
% npm install dotenv
```


あとはtypescriptでやる場合など、必要なものを各自インストールする。
(以降、コードはTypescriptで記述する。)

```
% npm install -save-dev typescript
```

# botを起動してみる

まずはbotの起動から。

今回は`src/index.ts`を作成し、この中で実行していく。

```ts:src/index.ts
import { Client, Intents } from 'discord.js';
import dotenv from 'dotenv';

dotenv.config(); // envファイルを読み込む

const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] }); // 1

client.once('ready', () => { // 2
  console.log('Ready!');
});

client.login(process.env.DISCORD_TOKEN); // 3
```

軽く説明
`dotenv.config();`はenvファイルを読み込むためのおまじない。

`(1)`ではdiscord botを操作するクラスを生成する。
なお、v12までとは違い、引数`ClientOptions`が必須となった。
その中でも`intents`のみ必須で、これはdiscord apiからどのイベントを受信するかを指定する。

どのフラグがどのイベントに対応しているかはこちら：
公式：[Discord Developer Portal — Documentation — Gateway](https://discord.com/developers/docs/topics/gateway#list-of-intents)
Discord.js Japan User Group：[Gateway Intents の利用に関するガイド](https://scrapbox.io/discordjs-japan/Gateway_Intents_%E3%81%AE%E5%88%A9%E7%94%A8%E3%81%AB%E9%96%A2%E3%81%99%E3%82%8B%E3%82%AC%E3%82%A4%E3%83%89)

今回はとりあえずメッセージのやりとりを目標にするため、`GUILDS`と`GUILD_MESSAGES`を設定した。[^1]

[^1]: メッセージの送受信だけだと一見`GUILD_MESSAGES`だけでいいように見えるが、それだと何故か動かなかったので２つ設定している。

`(2)`ではbotを稼働する準備ができたら呼ばれるイベント。`client.on`に関しては他にもメッセージの受信やコマンドの受信などで使う。

`(3)`ではbotにログインする。引数にはbotのトークンを渡す。今回は`.env`に`DISCORD_TOKEN=xxxxxxx`と書き込んでいるのでそこから読み込む。

これが出来れば一旦起動してみる。

```
% node .
Ready!
```

・Ready!と出てそのまま実行が続いていること
・discord側でオンライン表示になっていること

これが確認できれば起動はOK!

# メッセージを受信してみる

次はメッセージを受信してみる。ついでにおうむ返ししてみる。

readyイベントの次に以下を追加する

```ts:src/index.ts
client.on("messageCreate", (message) => {
  if (message.author.bot) { // 1
    return;
  }
  console.log(message.content); // 2
  message.channel.send({ // 3
    content: `受信したメッセージ：${message.content}`,
    reply: {messageReference: message.id},
    // allowedMentions: { repliedUser: false }, // 4
  });
});
```

これも軽く説明

`(1)`では、botからの送信を無視する。これを飛ばすと、botのメッセージも認識する。
ちなみに他のbotのメッセージを受信したい場合は、`message.author.id === client.user?.id`をif文に入れる必要がある。
これを忘れた場合は、自身のメッセージを認識し、それを返すという無限ループが起きてしまう。

`(2)`では、受け取ったメッセージ文をログに出力する。

`(3)`では、受け取ったメッセージをそのチャンネルに送信する。
contentが本文で、replyが返信先のメッセージID。
もしメンション通知を飛ばしたくない場合は、`(4)`のallowMentionsを追記する。

これで実行してみて、`受信したメッセージ：xxx`が返ってきたら成功。

# スラッシュコマンドを使ってみる

最近のbotによくあるスラッシュコマンドが、discord.jsのv13でようやく対応したので、それを使ってみる。

ちなみにグローバルコマンドとギルドコマンドの２種類あるのだが、テストする時はギルドコマンドを使用する。
と言うのも、グローバルコマンドはリアルタイムで反映されず、最長１時間待たないと変化しないため、何回もテストするには向いていないから。

## 権限を追加する

ギルドコマンド[^2]を追加する場合は、別でサーバーに権限が必要なので追加する。

[^2]: グローバルコマンドの場合は不要だと思う。確証はない

[Discord Developer Portal](https://discord.com/developers/applications)から、該当のbotの`Oauth2`から、`bot`と`applications.commands`の２つをオンにした状態でurlを生成し、botをディスコードサーバーに追加する(既にディスコードサーバーに入っている場合、一度脱退させる必要はない)

## コマンドを追加する

次はスラッシュコマンドをディスコードサーバーに追加するコードを追記する。
今回はボットの起動時に追加するので、`ready`イベントの中に記述する。

```ts:src/index.ts
client.once("ready", async () => {
  const data: ApplicationCommandData[] = [{ // 1
    name: "run_the_slash_command",
    description: "テストとしてコマンドを実行します。",
  }];
  const command = await client.application?.commands.set(data,'server id'); // 2
  console.log("Ready!");
});
```

コード解説

`(1)`は登録するコマンドを配列で登録する。
nameはコマンド名、必ず英語の小文字である必要がある。descriptionはコマンドの説明。下の方に表示される。
他にもオプションなどがあるが、詳しくは：[Registering slash commands | Discord.js Guide](https://discordjs.guide/interactions/registering-slash-commands.html#options)

`(2)`は登録するコマンドを追加する。
setの第1引数に`(1)`を、第二引数に登録するディスコードサーバーidを記述する。なお、第二引数を省略した場合はグローバルコマンドになる。
また、setの代わりにcreateを使うこともできる。この場合、第一引数は配列ではなく、単体のコマンド情報で、かつ既存のコマンドは削除されない。主に途中で追加するときに使う用だと思われる。

これが出来れば一度実行し、ディスコードサーバー内チャットでスラッシュコマンドが打てるか確認する。(なお、この時押しても`インタラクションに失敗しました`と出る)

## コマンドを受け取る

コマンドは受け取って初めて動作するので、その部分を作成する。

```ts:src/index.ts
client.on("interactionCreate", async (interaction) => { // 1
  if (!interaction.isCommand()) { // 2
    return;
  }
  if (interaction.commandName === 'run_the_slash_command') { // 3
	await interaction.reply('コマンドを実行しました！'); // 4
  }
  console.log(interaction);
});
```

コード解説
`(1)`で新規イベントを作成。

`(2)`で受信したinteractionがコマンドかどうかチェックし、コマンド以外を拒否する。

`(3)`では登録したイベントを判断し、`(4)`で該当コマンドに返信している。

# 終わりに

簡単にbotを作成できる部分をまとめてみた。
他にもいろんな機能があるので、一度ガイドやドキュメントをみるといいかもしれない。


# 参考サイト：

[Introduction | Discord.js Guide](https://discordjs.guide/)
[discord.js](https://discord.js.org/#/docs/main/stable/general/welcome)

[node.js - Missing Access for Slash Commands - Stack Overflow](https://stackoverflow.com/questions/65539808/missing-access-for-slash-commands)