Node-REDを使ってブラウザに喋った内容を各SNSに通知する

# はじめに
## やりたいこと
* スマホにしゃべるとメールやslackやlineやカレンダーに通知したい
* 基本的な使い方は関連記事を参照してください。

## Node-REDのまとめ記事
* [Node-RED系の記事のまとめ](https://qiita.com/kazutxt/items/8c72448eefcba56dabef)

## 動作環境
* IBM bluemix Node-RED : v0.20.5

## 免責事項
* 検証をして確実と思われる情報を載せておりますが、誤っている可能性もゼロではないので、参考程度にご利用ください
* 本来はコードと実行結果のコンソールログも載せるべきなのですが、数と量が多いので、一旦は画面イメージまでとさせてください。

# 本編
## 画面
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/b473d4ac-d1a2-d099-7dc2-b964bd25ef65.png)

## 処理の流れ
1. 画面を開くとWebSocketを使ってサーバとの通信を確立
2. ボタンを押すと音声を認識できる状態になる
3. WebSpeechAPIを使って、ブラウザに向かって話した内容を文字列化
4. 文字列をサーバに送る
5. 文字列をもとに条件分岐などをして、送信先SNSと送るデータを決める
6. SNSに送る
7. 送った旨のメッセージをWebSocketを使ってブラウザに返す
8. WebSpeechAPIを使って喋らせる

## フロー

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259228/d0408077-4b5d-a8a0-955f-eaa2db11fc5f.png)

## 各SNSへのアクセス
実際はググると山程でてきますし、ここに書いてもすぐに古くなるので、各APIを参照ください。
一応いまいま時点でどんなことをしたのかは残しておきます

* slack : IncommingWebHookを使い実現。URLに対してデータを送っています
* Line : DeveloperのMessaging APIを使って実現。WebHookにデータを送っています
* メール：シンプルにメールのノードで実現。
* googleカレンダー：googleカレンダーのノードを使って実現。node-red-node-googleの指示に従い、アカウントとアクセス許可などを設定

### 補足
* 送る文章は ~~面倒なので~~ 文章全体。↑の例だと `遅刻しますって LINE して` とすべて送られます。
* カレンダーは今から1時間(デフォルト)でスケジュールが作成されるため、あまり実用的でないです。
* はっきり言って、Node-REDからみると文字列を受け取り、SNSに通知して、文字列を送り返しているだけです！

## ブラウザ側の実装
このあたりを参考にさせていただきました
* [ブラウザで音声操作をする。（Speech Recognition API）] (https://qiita.com/PonDad/items/4ca433ad03efbf7499a2)
* [Webページでブラウザの音声認識機能を使おう - Web Speech API Speech Recognition](https://qiita.com/hmmrjn/items/4b77a86030ed0071f548)


本家の方とほとんど変わらないので、オリジナルの方を見ていただくほうが良いかと思いますが、一応重要な部分のソースです

``` js:ブラウザのhtml内のjs
    const startBtn = document.querySelector('#start-btn');
    const resultDiv = document.querySelector('#speak-text');

    SpeechRecognition = webkitSpeechRecognition || SpeechRecognition;
    let recognition = new SpeechRecognition();

    recognition.lang = 'ja-JP';
    recognition.interimResults = true;
    recognition.continuous = false;

    let finalTranscript = ''; 
    recognition.onresult = (event) => {
      let interimTranscript = '';
      for (let i = event.resultIndex; i < event.results.length; i++) {
        let transcript = event.results[i][0].transcript;
        if (event.results[i].isFinal) {
          finalTranscript += transcript;
        } else {
          interimTranscript = transcript;
        }
      }
      resultDiv.innerHTML = finalTranscript + '<i style="color:#ddd;">' + interimTranscript + '</i>';
    }

    // 文字列の送信
    recognition.onend = (event) => {
      recognition.stop();
      const speakText = $("#speak-text").text()
      sock.send(speakText);
    }

    // 開始ボタンのクリック
    startBtn.onclick = () => {
      speechSynthesis.cancel();
      $('#status').text('please speak');
      $("#speak-text").empty();
      $("#response-text").empty();
      finalTranscript = '';
      recognition.start();
    }

    //Websocketの確立と各種イベント
    var socketaddy = "wss://" + window.location.host + "/ws/speaker";
    $(document).ready(function () {
      sock = new WebSocket(socketaddy);
      sock.onopen = function () {
        $('#status').text('connected');
      };
      sock.onerror = function () {
        $('#status').text('error');
      };
      sock.onclose = function () {
        $('#status').text('closed please reload page!!')
      }
      sock.onmessage = function (evt) {
        $('#status').text('response received');
        $("#response-text").html(evt.data);
        var speakText = $("#response-text").text();

        // 発言を作成
        const uttr = new SpeechSynthesisUtterance(speakText);
        uttr.rate = 1.3;
        uttr.lang = "ja-JP";
        speechSynthesis.speak(uttr);
      };
    });
```
しゃべるデータをいったんhtmlにしてから取り出しているのは、 brタグ などを喋られないようにするためです(笑)
見栄えでは2行にしたいけど、タグを喋られると気持ちが悪いので。。。