Discordのボットで不用意に発した独り言がみんなに聞かれるのを防止する

この記事は[Wanoグループ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/wano-group)の5日目の記事になります。

つい先日から、EDOCODDEではリモートワークのツールとして、[Discord](https://discord.com/)が導入されました。Discordはゲームにおけるコミュニケーションでよく使われているツールですが、最近は、リモートワークのツールとしても、注目を浴びているようです。

`リモートワーク discord` でぐぐると色々でてきますね。

以下、前置きが長いので、コードが見たい人は、スクロールしてください。

## 当社でのDiscordの使い方

まず最初に、どのように使っているかをざっくり説明します。

以下のようなボイスチャンネルを用意しています。

* general ... みんなが一緒にいるチャンネル
* in-meeting ... zoom 等、Discord以外でミーティングしている時にはいるチャンネル
* lunch ... ご飯中に入るチャンネル
* leaving ... 外出したり、仕事終わったら入るチャンネル

それに加えて、ミーティング用のボイスチャンネルとして

* SAKURA
* AZUKI
* RURI
* MOEGI
* UNOHANA

といった名前のミーティング用のチャンネルがあります。

参加者の初期状態は

* general に参加
* ミュート状態
* スピーカーはオン

### Discordでミーティングしたいときの使い方

1. 声をかけたい時に、ミュートを解除し、誰かに声を書ける(みんなに聞こえる)
1. ミーティングルームに移動
1. ミーティングが終わったら general に移動(ミュートにする)

といったような感じで使っています。
誰かが誰かに用事があるんだなーとか、オフィスにいれば、聞こえてくるようことが、Discord上で再現できている感じです。

### Discord以外でミーティングしているときの使い方

Discord以外でミーティングをする場合(zoomや、Google Meet等)、in-meetingのチャンネルに移動して、ミュートにします。
そうしておくことで、視覚的に、ミーティング中なんだなーというのがわかるようになります。

## 問題点

### 1. 独り言を聞かれてしまう

ミーティングが終わったあとに、general に戻る時に、高確率でミュートに戻し忘れます(僕が)。
何が起きるかと言うと...

1. ミーティングがおわって、generalに戻る
1. ちょうどよく、可愛い猫の写真がSlackの雑談チャンネルにながれる
1. 「うわ、めっちゃかわいいなー。やばい。飼いたい」とかつぶやく
1. みんなに聞こえる

ちょっと恥ずかしいですね!

### 2. in-meetingでミュートにし忘れる

Discordのスピーカーはみんなオンの状態なので、他の人のミーティングに、関係ない別のミーティングの音声が混じることになります。

* zoom で AさんとBさんが話している
* 別の zoom で CさんとDさんが話している
* A,B,C,D は全員 in-meeting でスピーカーON

この時に、Cさんがミュートを解除してしまっていると、AさんとBさんの会議にCさんの声が混じってしまいます。

カオスですね!

## 問題点を解決するボットを作る

先程の2つの問題を解決するために、下記の機能を実現します。

1. ミーティングルームから general に戻った時にミュート解除状態だったらダイレクトメッセージで教える
1. in-meeting や leaving 等のチャンネルにいる時は、強制的にミュートにする

### 1. Discord Developer Portal でアプリを作る

この辺の手順は、そこらにあるので、ググってもらえれば良いかと思います。
下記の記事は英語ですが、簡潔にまとまってると思います。
https://discordpy.readthedocs.io/en/latest/discord.html

ざっくりまとめると

1. Appを作る
2. Botを作る
3. Oauth2 のURLを取得する
4. 3のURLをブラウザに貼ってログインする

途中で権限を選択しますが、この記事でやりたいことは、`Administrator` じゃないと無理なようです。
`Manage Server`でできるかなーと思ったのですが、権限エラーとなりました。

### 2. コードを書く

`discord.js` が必要なので、

```
npm install discord.js
```

しておいてください。

下記程度の簡単なコードで書けてしまいます。チャンネルIDは、ボットを手元に動かして、チャンネルに出たり入ったりして、調べて下さい。

```javascript
// ボットのトークンをここに貼る
const token = '*トークン*';
const Discord = require("discord.js");
const bot = new Discord.Client();

// ミーティング用のチャンネルIDを ... のところに入れる
const meetingChannel = {
    "....": true,
    "....": true,
    "....": true
};

// 強制ミュート用のチャンネルIDを ... のところに入れる
const muteChannel = {
    "....": true,
    "....": true,
    "....": true
};

bot.on('ready', () => {
    console.log(`Logged in as ${bot.user.tag}!`);
});

bot.on('voiceStateUpdate', (oldMember, newMember) => {
  // チャンネルIDを調べる用
  console.log(newMember.channelID);

  // チャンネルが変わったら
  if(oldMember && oldMember.channelID !== newMember.channelID) {
      if (muteChannel[newMember.channelID]) {
          // 強制ミュート
          newMember.setMute(true);
      } else {
          // ミュート解除
          newMember.setMute(false);
          // ミーティング用のチャンネルではないのに、ユーザーがミュートしてない場合
          if (!meetingChannel[newMember.channelID] && !newMember.selfMute) {
              // Muteを促す
              bot.users.fetch(newMember.id).then((user) => {
                  user.send("Muteし忘れてないですか？");
              })
          }
      }
  }
});

bot.login(token);
```

### 3. ボットを動かす

下記のように動かせば、ボットが動きます。

```
node App.js
```
ミュート解除したまま、general に移動すると...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11040/9c5ca8ed-ad03-ff05-4178-66ef368fcd64.png)

と、ダイレクトメッセージが来るようになりました。

leaving に入ると、赤いミュートマーク(サーバ側で強制ミュートしている状態)になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/11040/5dcde237-ea21-fdf4-7107-1362cd7a2eb7.png)

### ちょっとハマった所

ハマったと言うほどでもないのですが、

* StackOverFlowとかで紹介されている、`voiceStateUpdate` 使った例がだいたい間違えていた
* `client.users.get(ユーザーID).send("message")` でダイレクトメッセージを送れると書いているのが多かったが、使えない

ちゃんとドキュメント読みましょうという感じですね。

## おわり

という感じで、とても簡単にボットが作れました。
discord.js は、[ドキュメント](https://discord.js.org/#/docs/main/stable/general/welcome)を見ると、かなりAPIが充実しているので、色々遊べるのじゃないかなーという気がしています。

とりあえず、これで恥ずかしい思いはしなくなると思うので、一安心です。

----
Wanoは積極的にエンジニア採用を行なっています！
まずはオンラインでVPoEとのカジュアル面談から。お好きな入り口からお気軽にお声がけください！
Wano Recruitページ　https://group.wano.co.jp/recruit/
QiitaJobs　https://jobs.qiita.com/employers/wano-inc/postings/1297
Wantedly　https://www.wantedly.com/companies/wano/projects
Findy　https://findy-code.io/companies/522

EDOCODEも積極的にエンジニア採用を行っています!
興味のある方は下記を参照ください。
Green https://www.green-japan.com/job/94118