議事録取るのが苦手だから会話（自然言語）をSlackに記録するbotを作った

さて、アドベントカレンダーも続々と始まってきました。
改めて、はじめましての方ははじめまして。
インフラエンジニアの @morin_river です。
入社一発目のQiitaの記事なのでインフラエンジニアとして頼られるべく
頑張って記事を書きたいと思います。

え？すでにタイトルと乖離してるって？
たぶん、大丈夫です、たぶん……。


# はじめに

みなさん、議事録を取ってますか？
私はついつい、話に夢中になってしまうので、議事録を取るのが苦手です。
それが自動化されたら嬉しいですよね？

みたいな掴みを書こうとしてきたら @iwtn さんの
凄く真面目なドキュメントの話のアドベントカレンダーがRSSで回ってきました。
https://qiita.com/iwtn/items/0f0535082ea2ab701702

大変です、大事故ですがインフラエンジニアらしく
強い気持ちで気にせず、すすめて行くことにします。
（気持ち程度のインフラエンジニア要素）

また、完全に話が飛ぶのですが、趣味で最近TRPGにハマっています。
TRPGも、オンラインでやる場合会話の文字が自動的に記録されたら嬉しいですよね。
何回かTRPGのセッションを企画しているので
そんな時に文字がログとして残っていたら振り返った時、雰囲気がわかってたのしいかなと思いました。

などというわけで、会話の文字を起こすツールを作ってみました。

https://github.com/cahlchang/talkw2

Demo
https://cahlchang.github.io/talkw2/index.html

このツールはJavaScript内で完結するので、
GitHub pagesのように静的ページを提供している箇所で動かせば
完全に無料に使えます！
そしてなんと、マルチ言語対応です！！

…ところどころ、ソースコードがとても酷いのですが、プロトタイプということで見逃して下さい。
アルファ版作成まで、3時間程度でAPIのテストしながら作ってたので……。

# 使い方

Google Chromeを立ち上げDemoページか、上記ソースコードを配置済みのページに移動し
Slackの「Incoming Webhook」を取得し「Webhook Url」欄に貼り付けます。
そして、マイクをつなげ（もちろん、PCに内蔵されているものでかまいません）。
「RECORD START」ボタンを押すと、録音開始されます。
配置されたサイトがTLS対応されたページなら最初の1回、通常HTMLページなら開始ごとにマイクの許可を求められます。
その後は、自由に会話してください。
その内容がWebhookの取得ページ、またはツール上で記載されたチャンネルに投稿されます。

* サンプルデモ

![スクリーンショット 2018-12-03 01.27.31.png](https://qiita-image-store.s3.amazonaws.com/0/146781/b2a98a1e-9c35-b726-dd92-8e22c097dc98.png)

そこそこ誤変換はありますが、わりといい感じで記録出来ているかな？

# 技術スタック

* Google Chrome
* Slack
* JavaScrit
* jQuery

はい、ツッコミどころ満載ですよね。
なんだよ**Google Chrome**って！！

これには理由があるのです。
このツールでは、Web Speech APIというものを利用しています。
このAPIは、以下の記事が参考になります。

https://qiita.com/GalapagosOf/items/b0df73a0a53a29b40388

ざっくりまとめると、W3CによってWeb標準化が進められているものの
現段階では完全に動作するのはChromeだけのようです。
そして、上記資料によるとChrome内部で「Cloud Speech API」を利用している……ようです。
（Cloud Speech APIを使用しているという事の、一時資料は見つかりませんでしたが……）

Cloud Speech APIはGCPのサービスの1つなので、ちょこっとお金がかかります。
なので、Chromeに限定してWeb Speech APIを使っているのです。
　
　
　
えっ、jQuery？
それは……その、JavaScriptよくわかんなかったから、とりあえずDom操作できればいいかなって……。
（気力が湧けば、どこかで新しいのにはしたいな、とは思ってます）

## 裏話

このAPIにたどり着くまでには、少し時間がかかりました。
趣味レベルではMLや音声認識プログラムで遊んでいたりもしたので
ローカルマシン内部で完結する方法も探していました。
全部ひらがな＋すっごい雑な音声区切りレベルなら自前実装出来たのですが
漢字変換や精度向上を考えると、学習量が圧倒的に足りませんでした。

https://github.com/cahlchang/TalkWith/blob/master/py_analysis/run.py

このあたりで使っている内容と一緒なのですが、
日本語の子音と母音をパターン化して抜き出すアプローチをとっていました。

https://github.com/cahlchang/TalkWith/blob/master/py_analysis/run.py#L66-L76

また、このあたりにハードコーディングしているのですが
会話の周波数は個々人により違うので、それを全部網羅するには個人では大変過ぎました。

またjuliusなら、ある程度の精度が見えたのですが
それを汎用的に誰でも使えるように構築する方法や
サーバにお金をかけずに実装するプランが見えてきませんでした。
http://julius.osdn.jp/

そんな中、私は「ゆかりネット」というものを発見しました。
http://www.okayulu.moe/

もともと色々な動画サイトなどで使われていたのは知っていたのですが
初めてダウンロードして見てみたのでした。
……Windowsでインストール済みのファイルサイズで60MB！？
しかも、このツールには高度なGUIが付いているのに……。
さらに、使ってみるとjulius以上の精度が出ている気がする！？
（個人の感想です）。

私が書いた書きなぐりのプログラムとは比べるまでもありません。
このツールが使っているアプローチを調べようと思ったのでした。
その結果たどり着いたのが、このツールの方法でした。
ソースなどは公開されていないのですが、起動後Chromeが立ち上がり
同じような結果が得られるので、アプローチはだいたい同じなのかな？と思います。

さて、この勢いで買ったボイスロイド結月ゆかりは、どう活用しよう……。

# 解説

大体、技術スタックで解説している内容なのですが、順番に解説していきます。

## Webページ

CSSでいい感じにフォームを作ります。
はい、ごめんなさい。
雰囲気で作ったので……。

ここで投稿するパラメータを加工できるように入力欄を追加しておきます。

## 音声録音

```
    window.SpeechRecognition = window.SpeechRecognition || webkitSpeechRecognition;
    recognition = new webkitSpeechRecognition();
    var str_lang = $('input:radio[name="radio2"]:checked').val();
    recognition.lang = str_lang;
    recognition.interimResults = true;
    recognition.continuous = true;
```

まず、音声認識APIのインスタンスを有効にします。
各パラメーターは、以下のページを参考にしてもらえるとなんとなくわかると思います。

https://developer.mozilla.org/ja/docs/Web/API/SpeechRecognition

`continuous` パラメータを有効にすると、コールバック関数に状態が帰ってきて
少し冗長な表現になるので注意が必要ですね。

## 状態の保存

また、この状態でWebページの情報をCookieに保存するようにします。
このあたりは、gimKondoさんがコントリビュートしてくれました！
https://github.com/gimKondo
また、gimKondoさんはレコード停止機能も合わせて作ってくれました。
あわせあわせ、本当に感謝です！

## 文字の処理

そして、コールバック関数で適切に値を扱います。
このあたりは、まだ改善の余地はありそうなのですが
とりあえずイベントに終了フラグが付いていたら、その文字をslackに通知します。
終了フラグがつく前でも、webページ上のテキストボックスにいれておくと
なんか会話してるなーって感じがわかって良いと思います。
また、文字の動きかたを見ていると、裏側で起きてる処理が想像出来て楽しいです。
いやぁ、機械学習すごいなぁ……。

## Slack通知

ここでは、webページの情報をもとにpayloadを組み上げてslackにリクエストを送っているだけです。
長時間会話していると時刻が先頭に欲しくなったので追加してますが、それぐらいですね。

## その他

ここでエラーハンドリングをしていると、割と高確率でエラーになることがわかります。
これは、自然会話がおかしな状態で終わってしまった場合
「abort」エラーイベントとしてカウントされてしまうからのようです。
議事録とかでは会話が交差することなどもあると思うので
abortイベントはスルーするなどの作り込みをすると良いかもしれません。

# 大事なこと

## セキュリティ

これは、マイクの音声をslackに飛ばすので、外部から傍受などをされると危険かもしれません。
中間者攻撃を警戒して、HTTPS化されたサイトじゃないと毎回マイクの許可を求められるのだと思います。
また、これはブラウザに完全にセキュリティを任せているので
セキュリティリスクが発生します。
ただ、逆にいうとブラウザとSlack以外で文字は通らないので、
その二箇所を信用するなら安全なのかもしれないかな？と思います。

なんにせよ、このツールのセキュリティリスクに関しては私は責任を持てないので
そちらには注意をおねがいします。

ただ、このセキュリティリスクをはるかにふっとばす問題ごとがあります。

## 誤検知

このツールは、「そのままGoogleで学習された文言」を元に言葉を表現してると思われます。
そのため、誤検知をした場合にも日本語としてなんとか意味のある言葉にしようとしているようです。

* NGワード集
* 
![スクリーンショット 2018-12-03 00.53.59.png](https://qiita-image-store.s3.amazonaws.com/0/146781/b4ecfe0f-3643-87f9-12b7-011058fd9e98.png)


ここまでなら、楽しいのですが…
たまに、本当にダメなワードも入ってしまいます。
そしてその結果、**完全にアウトでえげつないFワード**が検出されます。

![スクリーンショット 2018-12-03 01.46.51.png](https://qiita-image-store.s3.amazonaws.com/0/146781/1e841692-8c62-d8b6-bdaa-7e4762947bf9.png)


結果、こんなことになります。
…これだと、過剰反応してるように見えるかもしれないのですが、本当にダメなワードだったのです。
とてもダメなワードを産んでしまうので、**絶対に会社など、オフィシャルな場所では使わない**ようにしましょう。

……はい、さてこのアドベントカレンダーのタイトルはなんだったでしょうか？