discord.js-v13でdiscordbot (スレッド周りの機能実装)

# はじめに

当記事は筆者の忘備録であり、完全なチュートリアルではないことをご理解ください。
スレッド周りのみを見たい方は[こちらを](#スレッド周りの実装)

# 開発環境

```
Node.js - 16.11.0
discord.js - 13.2.0
```

## 環境構築

[Node.js 公式サイト](https://nodejs.org/ja/)
[discord.js 公式Guide](https://discordjs.guide/#before-you-begin)


まずは適当な場所に新規フォルダを作成し、そのフォルダ内で

```
npm init -y
```

を実行、完了したら同フォルダ内で

```
npm install discord.js
```

上記二つが完了すればひとまず開発環境は構築できました。

#botの招待
[discord developer portal](https://discord.com/developers/applications)
### その１

ログイン後`New Application button`をクリックし、Appを作成してください

作成が完了したらページ左側にある`Bot`項目をクリックし、`Add Bot`をクリックします。

ここで`TOKEN`という項目と青色のボタンが現れると思うので左側の`Copy`をクリックしトークンを控えておいてください。

:::note alert
今控えたトークンは必ず他人に教えないでください。またGithub等にトークンが記載されたファイルを
上げることも控えてください。当警告を無視した結果起こりえる被害について筆者は責任を負いかねます。
:::

### その２

ページ左側の`OAuth2`をクリック

ここが**重要**で

`SCOPE`の中にある`bot`と`applications.commands`に必ずチェックを入れてください。
後々追加することも可能ですが思わぬエラーと無駄な時間を過ごすことになります。

チェックを入れると下側に`BOT PERMISSIONS`という項目が出てくるので
皆様の用途に合った設定をしてください。

全てにチェックを終えたら、先ほどの`SCOPE`にURLが表示されていると思うのでコピーし踏んでください。
これで導入は完了！

# ファイル作成
[discord.js Guide(Initial files)](https://discordjs.guide/creating-your-bot/#creating-configuration-files)

トークン管理ですが、ここではひとまずローカル環境での動作を目的としているので
最初に作成したフォルダのルートディレクトリに`config.json`ファイルを作成してください。
以下中身

```config.json
{
	"token": "先ほど控えたトークンを張り付けてください。"
}
```

`gitignore`も作成します。

```.gitignore
node_modules
.env
config.json
```

ようやくメインファイルである`index.js`を作成します。
以下中身

```index.js
const { Client, Intents } = require('discord.js');
const { token } = require('./config.json');

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

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

client.login(token);
```

作成が完了したらターミナル(コマンドプロンプト)で
`node index.js`
を実行してみてください。ターミナル上に`Ready!`と表示されたらひとまず作業は完了です。:relaxed:
終了は`Ctrl + c`です。

# コマンド作成

ここではv13から追加されたスラッシュコマンドを作成します。
まずは以下画像を見てください。
![54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/b3e38b42-ece5-c94a-7997-011c676c65f9.png)

このように`/`を入力するとコマンド一覧が現れますよね？botがいないサーバーで入力してもビルドインコマンドが表示されます。

これらを今から作成します。
ちなみにですが、[ここ](#その２)で`applications.commands`にチェックを入れていない場合次の作業を行った時に
**必ず**エラーが発生します。~~だからチェックをいれる必要があったんですね！~~

本題ですが、まずはプロジェクトフォルダ内で

```
npm install @discordjs/builders @discordjs/rest discord-api-types
```

を行ってください。これらはスラッシュコマンドを作成するときに必須な物です。

インストールが完了したら、[先ほど](#ファイル作成)作成した`config.json`を以下のように修正します

```config.json
{
	"clientId": "123456789012345678",
	"guildId": "876543210987654321",
	"token": "your-token-goes-here"
}
```

```
clientIdとguildIdはそれぞれ
- clientId ⇒ https://discord.com/developers/applications　のOAuth2タブにCLIENT IDが記載されていますのでそれを張り付けてください。
- guildId ⇒ botを参加させたサーバーのIDです。サーバーを開いて名前を右クリックするとIDをコピーと出てきますのでコピペ。
```

次に核となる`deploy-commands.js`を作成します。
以下中身

```deploy-commands.js
const { SlashCommandBuilder } = require('@discordjs/builders');
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const { clientId, guildId, token } = require('./config.json');

const commands = [
	new SlashCommandBuilder().setName('ping').setDescription('Replies with pong!'),
	new SlashCommandBuilder().setName('server').setDescription('Replies with server info!'),
	new SlashCommandBuilder().setName('user').setDescription('Replies with user info!'),
]
	.map(command => command.toJSON());

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

rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands })
	.then(() => console.log('Successfully registered application commands.'))
	.catch(console.error);
```

少しだけ解説をば

```
- setName ⇒ コマンド名。setName('ping')であれば/pingと入力することができるようになります。
- setDescription ⇒ コマンドの説明文です。先ほどの画像でコマンド名の下に出てきてたやつです。
```

オプションは他にも沢山あります。詳細は[こちら](https://discordjs.guide/interactions/registering-slash-commands.html#option-types)
後ほど記述するスレッド周りのコマンドも他オプションを利用します。

## 作成出来たら

ファイルがあるディレクトリで

```
node deploy-commands.js
```

を実行してください。完了したら自身のサーバーで`/`を入力してみてください。コマンド一覧が出てきます。

ただ、現状はコマンドを登録できただけで機能は実装していないので次項からいよいよ実装に入ります。

# コマンドに対しての機能実装

[公式ガイド](https://discordjs.guide/creating-your-bot/creating-commands.html#replying-to-commands)

ここではコマンドに対して起こる機能を実装します。`index.js`を開いて以下のように編集してください。

```index.js
client.once('ready', () => {
	console.log('Ready!');
});

//以下追加

client.on('interactionCreate', async interaction => {
	if (!interaction.isCommand()) return;

	const { commandName } = interaction;

	if (commandName === 'ping') {
		await interaction.reply('Pong!');
	} else if (commandName === 'server') {
		await interaction.reply('Server info.');
	} else if (commandName === 'user') {
		await interaction.reply('User info.');
	}
});

// ここまで

client.login(token);
```

一応解説


```
if (commandName === 'ping') {
      await interaction.reply('Pong!');

// もしコマンド名が 'ping' だったら 'Pong!' を返します
詳しくは以下画像を見てください。
```

![55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/476c03fe-7bc4-7511-c7fb-e46b4d01ea90.png)

interactionってなんぞや？って人は[ここ](https://discord.js.org/#/docs/main/stable/class/Interaction)を参照してください。

返信内容に引っ張ってきたデータを入れたい場合は`シングルクォーテーション`で囲うのではなく`バッククォート`で囲って

```
await interaction.reply(`Server name: ${interaction.guild.name}\nTotal members: ${interaction.guild.memberCount}`);
```

このように記述すると

![56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/6dc34fca-9c86-e75c-8fbc-d203d46d2593.png)

こんな感じで出せます。

# ひとまず・・・

以上で簡単なbotは作成完了しました。お疲れさまでした。
コマンドが増えていくとコマンド事にファイルを分ける等の作業が必要になりますがそれらはここでは解説しません。

次項からは応用編としてタイトルにもあるように`スレッド周りの実装`を行います。
元気な方は引き続き頑張りましょう。

# スレッド周りの実装

## はじめに

当項目はスレッド周りの実装をしてみようと思った矢先、日本語記事が全くなくとてもつらい思い(ドキュメント漁り)をしたので
こうして日本語記事として残そうと思った次第です。

## ソース

```index.js
else if (commandName === 'ct') {
        const thread = await interaction.channel.threads.create({
            name: `${interaction.options.getString('name')}`,
            autoArchiveDuration: 60,
            reason: 'Needed a separate thread for food',
        });

        interaction.reply({ content: `${thread.name}が立ちました。`, ephemeral: true });;
    }
```

```deploy-commands.js
new SlashCommandBuilder().setName('ct').setDescription('create thread').addStringOption(option => option.setName('name').setDescription('スレッド名を入力してください。'))
```

## 解説

まずは`deploy-commands.js`の方から

```deploy-commands.js
.addStringOption(option => option.setName('name').setDescription('スレッド名を入力してください。'))
```

`.addStringOption`でコマンドに対して以下の画像のようにオプションをつけることができます

![57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/6b5b47e0-7146-fb12-049c-40554c512ec9.png)

ここではオプションの名前を`name`としています。
ここで入力された`hello`は後ほど使います。

続いて`index.js`

```index.js
if (commandName === 'ct') {
        const thread = await interaction.channel.threads.create({
            name: `${interaction.options.getString('name')}`,
            autoArchiveDuration: 60,
            reason: 'Needed a separate thread for food',
        });

        interaction.reply({ content: `${thread.name}が立ちました。`, ephemeral: true });;
    }
```

```
await interaction.channel.threads.create
// コマンドが送信されたチャンネルにスレッドを作成します
```

以下`channel.threads.create`のオプション

```
name: `${interaction.options.getString('name')}`
// interaction.options.getString('ここに(option.setNameで付けた名前を入れます)')
// こうすることで先ほどオプションで入力させた'hello'がこれから立ち上がるスレッドの名前になります
```

```
autoArchiveDuration: 60,
// 何分で自動アーカイブするかの設定です
```

```
interaction.reply({ content: `${thread.name}が立ちました。`, ephemeral: true });;
// この部分、実はなくてもスレッドは立ちますがこれを記述しないと以下画像のようにエラーを吐きます。
// ephemeralは初期値だとFalseですがtrueにすることでbotからのメッセージがコマンドを打った人間にしか見えなくなります。
```

![51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2175468/1ecb5e1d-d74e-b817-5f69-bcd250e0bac5.png)



# まとめ

ここまで見てくださった皆様、お疲れ様でした。そしてご覧いただきありがとうございます。
雑な解説ではありましたがどなたかのお役に立てたなら幸いです。
私自身`discord.py`を過去に使っていて最近`discord.js`にやってきた身なのでまだまだ分からないことだらけですが頑張ってこれからも開発していけたらなぁと思っています。

また、そのうち本記事では紹介できなかった部分も記事に起こすつもりなのでまた良ければお願いします。

# さいごに

当記事は公式のGuideをもとに個人的な解釈を含めて解説したものです。
なのでもちろん正しい完璧な記事とは言えないのでもし問題・質問等ありましたらお気軽にコメントしてください。