discord.js v12でVCの入退室に反応するbotを作るメモ

はじめに
===

discord.jsを使ったbotの資料はたくさんありますが、

- v12対応
- voiceStateUpdateを利用
- VCの入退室に関連した資料

というのが殆どなかったのでメモっときます。**~~v11対応だったりErisだったりするとあるんだけどdiscord.js v12がいいんだよ！！~~**

このページに載っている情報でなにができる？
===

- 入退室時、ミュートなどの状態を変更したときに関数を発火させることができる
- 状態を変化させたユーザやチャンネル、ギルド(サーバ)などの情報を取得することができる
 


環境
===
Node.js v14.17.0
discord.js v12.5.3

サンプルコード
===
```js
// discord.js モジュールのインポート
const Discord = require('discord.js');

// Discord Clientのインスタンス作成
const client = new Discord.Client();

// トークンの用意
const discord_token = `hogehogehoge`; //ここは各自のトークンを入力

client.on("voiceStateUpdate",  (oldState, newState) => {
    if(newState && oldState){

        //newState関係
        console.log(`NEW:userid   : ${newState.id}`);       //ユーザID
        console.log(`NEW:channelid: ${newState.channelID}`);//チャンネルID、nullならdisconnect
        console.log(`NEW:guildid  : ${newState.guild.id}`); //ギルドID

        //oldState関係
        console.log(`OLD:userid   : ${oldState.id}`);       //ユーザID
        console.log(`OLD:channelid: ${oldState.channelID}`);//チャンネルID、nullならconnect
        console.log(`OLD:guildid  : ${oldState.guild.id}`); //ギルドID
        
        if(oldState.channelID===newState.channelID){
            //ここはミュートなどの動作を行ったときに発火する場所
            concole.log(`other`);
        }
        if(oldState.channelID===null && newState.channelID != null){
            //ここはconnectしたときに発火する場所
            concole.log(`connect`);
            }
        }
        if(oldState.channelID !=null && newState.channelID === null){
            //ここはdisconnectしたときに発火する場所
            console.log(`disconnect`);
            }
        }
    }
});

// Discordへの接続
client.login(discord_token);
```

コード解説
===
##discord.jsを使うに当たって当然必要な物たち

```js
// discord.js モジュールのインポート
const Discord = require('discord.js');

// Discord Clientのインスタンス作成
const client = new Discord.Client();

// トークンの用意
const discord_token = `hogehogehoge`; //ここは各自のトークンを入力

～～中略～～

// Discordへの接続
client.login(discord_token);
```
- この辺りはおまじない的役割
- token流出に注意しましょう


##voiceStateUpdate
```js
client.on("voiceStateUpdate",  (oldState, newState) => {
～～中略～～
});
```
このページの肝となる場所です。
voiceStateUpdateをdiscordより受信すると、この関数が動きます。

###voiceStateUpdateを受信する条件

>botが参加しているサーバ(guild)で

>- ユーザがVCに参加する
- ユーザがVCから退出する
- ユーザが各種ミュートをする

の模様。

###oldState,newStateとは何ぞや？
[document](https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=e-voiceStateUpdate)を見るとこんなことが書いてあります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280107/5cab15bf-ef93-bb31-8ddd-93b51d4ff0fa.png)

これしか書いてないです。**~~説明雑では？~~**
oldStateが変化前の状態で、newStateが変化後の状態です。
VoiceState型でデータをくれるみたいですね。

###VoiceState型とは？

[document](https://discord.js.org/#/docs/main/stable/class/VoiceState)にはたくさん情報があるのですが、はっきり言って読みづらいですね。

今回は、

- 状態の変化したチャンネルID取得
- 状態の変化したサーバ(guild)のID取得
- 状態の変化したユーザID取得 

この3つに絞って説明します。

```js
        //newState関係
        console.log(`NEW:userid   : ${newState.id}`);       //ユーザID
        console.log(`NEW:channelid: ${newState.channelID}`);//チャンネルID、nullならdisconnect
        console.log(`NEW:guildid  : ${newState.guild.id}`); //ギルドID

        //oldState関係
        console.log(`OLD:userid   : ${oldState.id}`);       //ユーザID
        console.log(`OLD:channelid: ${oldState.channelID}`);//チャンネルID、nullならconnect
        console.log(`OLD:guildid  : ${oldState.guild.id}`); //ギルドID
```
ただ表示するだけの部分ですが、以下のように取得することができます。

|   |oldState|newState|備考|
|:-:|:-:|:-:|:-:|
|ユーザーID|oldState.id|newState.id|newState.guild.members.resolve(newState.id).user.username とかでユーザ名を取得できる。|
|チャンネルID|oldState.channelID|newState.channelID|nullになる場合あり。newState.channel.name とかでチャンネル名を取得できる。|
|ギルドID|oldState.guild.id|newState.guild.id|このIDでサーバごとの設定変更とか状態保持とかするといいかも|

さらに、oldStateとnewStateのchannelIDの状態を確認することで以下のように判別できます。

|oldState.channelID|newState.channelID|状態|
|:-:|:-:|:-:|
|チャンネルID|チャンネルID|ミュートなどをした場合。<br>oldStateとnewStateでチャンネルIDが別IDとなっていると、VC間の移動を検出できるようです。|
|null|チャンネルID|入室|
|チャンネルID|null|退室|


ということで、

```js
        if(oldState.channelID===newState.channelID){
            //ここはミュートなどの動作を行ったときに発火する場所
            concole.log(`other`);
        }
        if(oldState.channelID===null && newState.channelID != null){
            //ここはconnectしたときに発火する場所
            concole.log(`connect`);
            }
        }
        if(oldState.channelID !=null && newState.channelID === null){
            //ここはdisconnectしたときに発火する場所
            console.log(`disconnect`);
            }
        }
```

このようにコードを書くことができます。


以上、実際にDiscordの動作に反応すれば完了です。