【実用編】【DB】discord.js v13 ユーザーごとにプロフィールを作成する

## 前書き
私がメインで開発している「[ヒトリン](https://discord.com/api/oauth2/authorize?client_id=876116418037444630&permissions=545460846583&scope=bot%20applications.commands)」というボットにて、最近ユーザーごとにプロフィールを作成する機能を追加したため紹介します。

## どのようなものか
MongoDBを使用し、ユーザーごとに様々な情報を格納したデータを作成し、プロフィールを作成する。
今回は、MongoDBの使用方法からプロフィールを作成しコマンドで表示させるところまでのフルフルバージョンを紹介する予定でござんす。

## MongoDB

https://www.mongodb.com/

上記のリンクからMongoDBのサイトへリンクしサインインまたは新規登録を完了してください。
ほとんど英語ですが頑張ってどんどん次に進みましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/ea411e6f-137c-edc8-1475-810db2739f2a.png)

一番右の「FREE」となっているものを選択し、「CREATE」をクリックしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/2058f74c-d9f8-35ba-7f7d-ae44146f042a.png)

ここら辺は任意で選択してください。（なんもいじんなくても良いかもです）
右下にある「Create Cluster」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/26138f97-0b86-793c-de93-8cbceb0f4e6d.png)

ここら辺も適当でいいんですが、最後の「Where would you like to connect from?」は注意。（訳：あんちゃんはどっから接続するんや？）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/82d80f95-2c2b-af60-3375-4635dd7f23ac.png)

特に理由がなければには右の「Add My Current IP Address」をクリックしてください。（意味：現在のIPアドレスを追加）

:::note warn
注意
IPアドレスを変更した場合、ここの設定から使用中のIPアドレスを追加してください。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/66c1fe16-bd67-0396-5054-29d7f1b985a9.png)

完了したら「Finish and Close」をクリックしてください。


このような画面になれば設定は完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/c635c4a4-50af-ae55-65c6-1511bebe05bd.png)

## npm
MongoDBをNode.jsで使用するために「mongoose」をインストールします。

```console:cmd
npm install mongoose
```

## コーディング
いよいよコーディングをしていきます。コーディングをしたくてしたくてたまらなく **☆禁断症状☆** が出ていた方もいらっしゃることかと存じます。ご安心ください。

ボットの核となるファイル「`index.js`」等に記述をしていきます。

```javascript:index.js
const mongoose = require('mongoose');

~
~
~

mongoose //mongooseについて
	.connect(<接続するために必要>, {
		useNewUrlParser: true, //任意
	})
	.then(() => {
		console.log('データベースに接続したんだゾ');
	})
	.catch((error) => {
		console.log(error); //エラー出力
	});

```

`<接続するために必要>` のところに入れるものを説明します。

先ほどのこの画面に来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/1a4f8af2-4396-30ff-6bb0-b3e52e10fb4b.png)

「Connect」をクリックし、「Connect your application」をクリック。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/a0f6bd35-85ce-245a-fcde-cd2aa9bd0c52.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/b5df9581-ea58-450b-e120-9154cbbacee1.png)

「Node.js」で、バージョンを任意のものに選択したら以下のようなよくわからない文字列が出てきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1940116/c9be619f-a5ef-894c-177e-e7286a8dc02f.png)

```html:今回の例
mongodb+srv://test:<password>@cluster0.3afev.mongodb.net/<myFirstDatabase>?retryWrites=true&w=majority
```
`<password>` には先ほど設定した「ユーザー」と「パスワード」の「パスワード」を入力
`<myFirstDatabase>` には任意のデータベースの名前を入力

そしたらその文字列を `<接続するために必要>` のところに入れます。

```javascript:index.js
mongoose //mongooseについて
	.connect(mongodb+srv://test:<password>@cluster0.3afev.mongodb.net/<myFirstDatabase>?retryWrites=true&w=majority, {
		useNewUrlParser: true, //任意
	})
	.then(() => {
		console.log('データベースに接続したんだゾ');
	})
	.catch((error) => {
		console.log(error); //エラー出力
	});

```

そしたら次に任意のフォルダを作成し、そのフォルダの中に任意のJSファイルを作成します。今回は「`models`」というフォルダに「`profileSchema.js`」というJSファイルを作成します。
`profileSchmea.js` にはデータベースにどのようにどんな種類のデータを保存するのか、というものを書いていきます。
今回はユーザーのID、ユーザーネーム、アバターを保存していきます。

```javascript:profileSchema.js
const mongoose = require('mongoose'); //mongoDBを使用するためのおまじない

const profileSchema = new mongoose.Schema({
	_id: { type: String }, //ユーザーID
	name: { type: String }, //ユーザーネーム
	avatar: { type: String }, //アバター
});

const model = mongoose.model('Profiles', profileSchema);

module.exports = model;
```
`type: String` 等の説明
`type:` の後には型が入ります。まぁプログラミング勉強してる方ならほぼ誰でもわかるかと思いますが一応紹介しておきます。

* `String`: 文字列（"あいうえお" "僕は天才、人生満喫中" 文字同士の計算はできない `例："2" + "3" = "23"` )
* `Number`: 数字（1とか2とか。数字同士は計算できる `例：1+9=137193821738` ←嘘)
* `Boolean`: 真偽（「はい（True）」か「いいえ（False）」の二択）

などがあります。
ちな「**False**」の読み方は「ファルス」ではなく「**フォールス（フォルス）**」です。
「**NULL**」は「ヌル」ではなく「**ナル**」です。だって「ヌル」だと「NULL NULL」の読み「ヌルヌル」じゃないですか...w
読み方を間違えると意外と恥ずかしいので一応勉強しておいて損はないかもです。そんなことよりコードの勉強した方がいいんですけどね。羞恥心にはかなわねぇや。

ところで「ユーザーID」って言ってんのになんで文字列の「String」を使ってんだ？おめぇーって思った方。
(???) **良い質問ですね**
IDってなんか番号っぽいから数字の「Number」使えよってお思いでしょう。
実は以下の理由があります。

* Stringのほうが情報量多め
* どんなフォーマットで対応できるように
* 文字列にする！って決めたほうが楽

（ 引用：https://qiita.com/sato-shin/items/0ec486d9c1bf98d9bf4c ）


このようにすることでユーザープロフィールをどのようにデータベースに保存するかを決められました。

次に、このデータベースを実際に保存・使用していきたいと思います。

基本的にいつ保存するかというのは自由なんですが、私はコマンドを実行する「`index.js`」の`interactionCreate`内の上部に書いています。
※ちなみに`interactionCreate`内の上部に書いても、実際に動作するのはコマンドを実行するときのみです。

```javascript:index.js
const profileModel = require('./models/profileSchema'); //先ほど作成したスキーマを参照
~
~
~
client.on('interactionCreate', async interaction => { //メッセージを受け取ったら
	const profileData = await profileModel.findOne({ _id: interaction.user.id });
	if (!profileData) {
		const profile = await profileModel.create({
			_id: interaction.user.id, //ユーザーID
			name: interaction.user.username, //ユーザーネーム
			avatar: interaction.user.displayAvatarURL({ format: 'png' }), //アバター
		});
		profile.save();
		console.log('データベースに保存したよ: ' + interaction.user.tag); //一応ログとしてコンソールに出力
	}
	if (!interaction.isCommand()) return;

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

	if (!command) return; //コマンド以外無視
});
```

:::note alert
ガチ気を付けてマジンコマジマジ
async / await の非同期処理で行わないとエラーが発生せず、正常に動作しない可能性があります。これで僕は何度も苦しめられたんです！！！皆さんは気をつけましょうね！！！！！！絶対にな！！！絶対にﾀﾞｿﾞ！！！！
:::

`<任意モデル>.create({ ﾅﾝﾁｬﾗｰ })` で新しく作成できます。（設定によっては重複エラー発生するときありますので注意）
`<任意モデル>.findOne({ 検索 })` でその検索に当てはまるものを一つ取り出します。
`<任意モデル>.findOneAndUpdate({ 検索 }, { ﾅﾝﾁｬﾗｰ })` で検索に当てはまるものを取り出し、それにﾅﾝﾁｬﾗｰします。はい。
**思ったより割と簡単でしょ？**

ということでこれで作成とかは完了しました。そしたら今度はコマンドで表示してみましょう。
今回は「`profile.js`」というJSファイルを作成し表示させてみます。

```javascript:profile.js
const profileModel = require('../models/profileSchema');
const { SlashCommandBuilder } = require('@discordjs/builders');
require('dotenv').config();

module.exports = {
	data: new SlashCommandBuilder()
		.setName('profile')
		.setDescription('指定したユーザーのプロフィールを表示します。')
        .addUserOption(option => option.setName('対象').setDescription('ユーザーを選択')),
	async execute(interaction) {
        const user = interaction.options.getUser('対象'); //コマンドオプションのユーザー選択から
        const profileData = await profileModel.findOne({ _id: user.id }); //ユーザーのIDでデータベースから合うものを一つ取り出す。

		await interaction.reply(`ID: ${profileData.id}\nユーザーネーム: ${profileData.name}\nアバター: ${profileData.avatar}`);
	},
};
```
上記のようなコードを記述することでプロフィールを表示するコマンドが作成できます。
これだけではまだ「ユーザーの情報を表示する」みたいなコマンドだけで充分ですが先ほどのスキーマをいろいろといじれば標準機能よりもはるかに便利で交流的なプロフィールを作成できるはずです！
ぜひみなさんもプロフィール機能を自分なりに作ってみてください！

## 宣伝
私がメインで開発しているBOT「ヒトリン」。
...使用してみませんか？
開発中でまだまだ「くそ便利すぎマジワロタ」とまではいかないですが、ぜひみなさんと成長していけるようなBOTを作成したいと思っています。
ぜひご検討ください。

https://discord.com/api/oauth2/authorize?client_id=876116418037444630&permissions=545460846583&scope=bot%20applications.commands