v12でのdiscord.jsを用いた録音

#はじめに
　discord.jsを用いてボイスチャンネルの内容を録音する方法をご紹介します。
　ネット上の日本語文献でも、後ほど紹介する参考ページを含め、この内容に関する情報はいくつかあるのですが、どれも対応しているdiscord.jsのバージョンが古い（v11）ため、執筆時点で最新であるv12を用いた録音機能の実装をご紹介します。
##記事の対象
　この記事では、node.jsとdiscord.jsの導入や基本的な使い方についてはご紹介しません。導入がまだの方は、こちらのページ（[https://qiita.com/cryptocoin_harumaki/items/5d8c503e02093eca1f9b](https://qiita.com/cryptocoin_harumaki/items/5d8c503e02093eca1f9b)）などを参考にしてください。ただし、最新版のdiscord.jsを使うためには、node.jsのバージョンがv12以上である必要がありますので、古いバージョンをお持ちの方は、予めアップデートしておいてください。
#ボイス機能のための下準備
　discord.jsでボイス機能を使用するためには、対応したライブラリをインストールする必要があります。必要なパッケージは`@discordjs/opus`または`opusscript`のいずれかで、公式では前者が推奨です。また、`ffmpeg-static`もあるとよいとのことです。
　これらのパッケージは、いずれも`npm install`コマンドを使ってインストールできるので、インストールは簡単です。(もしWindowsの方でエラーを吐く場合は、`npm install --global --production --vs2015 --add-python-to-path windows-build-tools`を回してからインストールすると解決する場合があるようです)
#録音機能の実装
　ここからの手順は、こちらのサンプルプログラム（[https://gist.github.com/alx-xlx/b3b90bc96c33d02e080a4a96bc231d6e](https://gist.github.com/alx-xlx/b3b90bc96c33d02e080a4a96bc231d6e)）に沿って説明していきます。
(※2020/12/13改訂：[旧リンク]([https://gist.github.com/eslachance/fb70fc036183b7974d3b9191601846ba])がリンク切れのため、差し替えを行いました)
　まずは、サンプルプログラムの「v9-voice-receive.js」を丸々プロジェクトフォルダにダウンロードしましょう。それから、必要なファイルの設定やバージョンアップへ対応するためのコード書き換えを行っていきます。
##auth.jsonの作成
　サンプルプログラムをDLしたら、同じ階層に「auth.json」というファイルを作成してください。内容は、以下の通りに入力すればOKです。

```json:auth.json
{
    prefix: "rec_",
    token: "Your Token"
}
```
　prefixは、Discordで録音開始/終了のコマンドを使うときコマンド名の前につける文字列です。Tokenは、[Discord Dev Portal](https://discord.com/developers/applications)から「Bot」の項目に行くとコピーできる値です。Botを作ってからでないと入手できないので、先に上のリンクからアプリケーションの登録とBotの作成を済ませておいてください。
##コードの書き換え
　書き換えの内容は、4種類あります。単純な方から順に直していきましょう。
###メッセージ送信関数（34,44行目）
　メッセージの送信関数`ch.sendMessage(~~)`は、v12から対象外となりました。`sendMessage(~~)`の部分を`send(~~)`に書き換えましょう。
###find関数（21,53行目）
　チャンネル名をつかってチャンネルの一覧から該当するチャンネルを得るために`Guild.channels.find("name",ch_name)`という形で検索が行われていますが、v12では`Guild.channels`の仕様が変更になっています。（詳細は省きますが、`GuildChannelManager`というクラスになりました）
　新しい書き方では、`Guild.channels.cache.find(ch => ch.name === ch_name)`が、かつての`Guild.channels.find("name",ch_name)`と同じ意味になるので、この書き方に従って書き換えを行いましょう。
　具体的には、`msg.guild.channels.find("name", channelName.join(" "))`を`msg.guild.channels.cache.find(ch => ch.name === channelName.join(" "))`に書き換えればOKです。
###VoiceReceiver関連（30,36行目）
　VoiceReceiverに関連する処理について、小さな変更がいくつかあるため、ご紹介します。
　まず、30行目の`conn.createReceiver()`は`conn.receiver`として、メソッドからプロパティになりました。
　続いて、36行目の`receiver.createPCMStream(user)`は、`receiver.createStream(user,{mode:'pcm',end:'manual'})`となります。第2引数の「mode」プロパティは、音声をPCM形式で受け取ること、「end」プロパティは、自動で録音が終了されないことを表します。なお、発言が止まるたびに録音を停止したい場合は、「end」プロパティをデフォルトのままにしておいてください。
###空音声の再生処理
　discord.jsでは、音声を受信する前にBot自身が音声再生を行う必要があるようです。そのため、27行目からの

```js
voiceChannel.join().then(conn=>{/*ココ*/})
```
のコールバック部分で一度無音の音声を流す処理を実装します。
　初めに、無音音声を流すためのクラス`Silence`を実装してから、そのクラスを使って音声再生する処理を実装します。
　まず、`Silence`クラスですが、実装は以下の通りです。

```js
const {Readable}=require('stream')

class Silence extends Readable{
  _read(){this.push(Buffer.from([0xF8,0xFF,0xFE]))}
}
```
　内容としては、音声を含むデータを表すクラス`Readable`を継承し、`_read()`関数にOpusエンコードされた無音データを送信する実装となっています。
　続いて、音声再生の処理を書き加えます。コードは単純で、

```js
conn.play(new Silence,{type:'opus'});
```
です。これを、27行目

```js
voiceChannel.join()
  .then(conn=>{
    /*ココ*/
  })
```
の後ろに挿入してください。
（参考：[https://github.com/discordjs/discord.js/issues/2929#issuecomment-460052671](https://github.com/discordjs/discord.js/issues/2929#issuecomment-460052671)）
#Botを使ってみる
　ここまでの変更が上手くいっていれば、`node v9-voice-receive.js`コマンドを叩くことでBotが動作を始めます。
　Botを導入したDiscordサーバーの適当なテキストチャンネルで「rec_join ボイスチャンネル名」と入力するとBotが指定されたボイスチャンネルに参加します。その状態で発言を行うと、録音が開始されます。
　録音の終了は「rec_leave」とテキストメッセージを送るか、録音されている人がボイスチャンネルから抜けるタイミングになります。
　上手く行っていれば、プロジェクトフォルダ内の「recordings」フォルダに、拡張子「pcm」のファイルが出来ているはずです。
#録音データの再生
　最後に、録音されたデータを再生する方法をご紹介します。
　通常の音楽再生ソフトの多くは、このBotで録音されるデータに対応していません（データ形式などを規定するヘッダーが付いておらず、生の波形データのみを持つファイルです）。そのため、Audacityなどの音声編集ソフトが必要になります。ここでは、Audacityでの開き方のみご紹介するので、他のソフトをお使いの方は、各ソフトのマニュアル等をご覧ください。
　Audacityでは、画面上部のメニューで「File>Import>Raw data」を選択します。すると、ファイルの詳細な情報を求めるダイアログが表示されます。ここに、以下に説明する内容を順に入力して、「Import」を選択します。
####入力内容
- Encoding：Signed 16-bit PCM
- Byte order：Little-endian
- Channels：2 (Stereo)
- Sample rate：48000Hz

（参考：[https://manual.audacityteam.org/man/file_menu_import.html](https://manual.audacityteam.org/man/file_menu_import.html)）
#おわりに
　ここでご紹介した機能は、公式にはサポートされていない方法のようであることをここに補足しておきます。
　discord.jsを用いた音声録音等の処理について詳しいことを知りたい方は、英語になりますが、こちらのガイド（[https://discordjs.guide/voice/receiving-audio.html](https://discordjs.guide/voice/receiving-audio.html)）が親切なので、よろしければ目を通してみて下さい。