google-home-notifier周りをほぼ自動化した

# はじめに
GoogleHomeMiniを買って意気揚々とGoogle先生に喋らせようと思ったら意外に融通が利かないのでラズパイ買って[google-home-notifier](https://github.com/noelportugal/google-home-notifier)を入れた人も多いのでは無いだろうか。
そんな方で以下のような経験をした人はいないだろうか。


満を持してgoogle-home-notifierを起動すると下のようにngrokのサーバーが自動で立ち上がり、

```
GET example:
curl -X GET https://xxxxxx.ngrok.io/google-home-notifier?text=Hello+Google+Home
POST example:
curl -X POST -d "text=Hello Google Home" https://xxxxxx.ngrok.io/google-home-notifier
```

curl叩いてGoogle先生喋ってくれて、よゆーやん、と思ってたら、
**サービスを立ち上げるたびにngrokのURLが変わってしまう**ことに気づき絶望した人。(前振りが長い..)

これだとサーバーを起動しなおしたり、予期せぬ再起動が発生したときにいきなり繋がらない、となってしまう。

なので、今回はこの問題を解消する

*2018/5/23 追記「8時間でURLが失効しないようにする」*
*2018/12/15 追記「google-tts-apiを最新にする」*

## 大雑把な方針
まずgoogle-home-notifier自体はnodejsなのでforeverを使って永続化する。さらにラズパイ起動時にそれを呼び出すようにすれば常に起動状態が維持されるようになる。

次に問題のURLについて。こいつはgoogle-spreadsheetを使うことにした。常に最新のURLをgoogle-spreadsheetに記録しておくことで、各サービスはそのシートを参照することで常にアクセスできるようになる、という寸法

# google-home-notifierを永続起動させる
[node.jsスクリプトの自動起動](https://eguchi.jp/blog/?p=831)を参考にforeverをインストールして、ラズパイ起動時に自動で立ち上がるようにした。ソースはこんな感じ

```sh:/etc/rc.local
# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

# 以下の1行を追加
sudo -u pi /usr/bin/node /usr/bin/forever start -p /var/run/forever --pidfile /var/run/node-app.pid -l /home/pi/google-home-notifier/out.log -a -d /home/pi/google-home-notifier/example.js 
exit 0
```

あとはラズパイを再起動してあげて、サービスが起動しているかを確認すればOK

```
sudo reboot
forever list
```

```logtalk
info:    Forever processes running
data:        uid  command         script                                   forever pid id logfile                               uptime        
data:    [0] tYP0 /usr/bin/nodejs /home/pi/google-home-notifier/example.js 638     676    /home/pi/google-home-notifier/out.log 0:0:26:39.768 
```

# google-home-notifierからgoogle-spreadsheetにURLを書き出す

## API認証キーを作成する
下のページを参考にAPIの認証キーを作成する。
使用するAPIは**Google Sheets API**。
http://www.yoheim.net/blog.php?q=20160411
作成されたJsonの認証キーはラズパイの方で使うので転送しておく。

また、作成時に発行されるサービスアカウントIDをコピーしておくこと。
サービスアカウントIdってのは`<プロジェクト名>@<プロジェクトId>.iam.gserviceaccount.com`みたいなやつ。

コピーし忘れた場合は、「認証情報」タブ→「サービスアカウントの管理」から確認できる。

## GoogleSpreadSheetを作って共有設定をする
Google Driveで任意のspreadsheetを一つ作り保存する。中身は空欄でOK
次に「共有」→入力欄にサービスアカウントIdをいれる。
<img width="550" alt="スクリーンショット 2017-12-10 22.50.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/76726/1f079d50-1027-f1be-5281-958325175e32.png">
メールを送るかとか聞かれるが「いいえ」でよい。
付与する権限を「編集者」にする。
設定が終わったら完了を押す。

これでアカウントIdに紐付いた認証キーからは編集可能になった。

最後にスプレッドシートのKeyをコピーしておく。URLの以下のxxxの部分
`https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit#gid=0`

## example.jsを修正してURLを書き出せるようにする
ほぼこの記事「[node-google-spreadsheetを使う(超簡易的なテスト)](https://qiita.com/daikiojm/items/b7998c94b174f474d13f)」を参考にした。

使うモジュールをインストール
`npm i google-spreadsheets`

以下のようにexample.jsを修正

```js:example.js
var ngrok = require('ngrok');
var bodyParser = require('body-parser');
var GoogleSpreadsheet = require('google-spreadsheet');
var ngrokUrlSheet = new GoogleSpreadsheet('xxxxxxxxxxxxxxxxxxxxxxxxxxx'); //コピーしたスプレッドシートのKey 
var credentials = require('./GoogleHomeNotifier-xxxxxxx.json'); //作成した認証キーへのパス
var app = express();
const serverPort = 8091; // default port

var deviceName = 'Google Home';
var ip = '192.168.xx.xx'; // default IP

var urlencodedParser = bodyParser.urlencoded({ extended: false });

var sheet;
ngrokUrlSheet.useServiceAccountAuth(credentials, function(err){
   ngrokUrlSheet.getInfo(function(err, data){
      sheet = data.worksheets[0];
   });
});

// 中略

app.listen(serverPort, function () {
  ngrok.connect(serverPort, function (err, url) {
    console.log('Endpoints:');
    console.log('    http://' + ip + ':' + serverPort + '/google-home-notifier');
    console.log('    ' + url + '/google-home-notifier');
    console.log('GET example:');
    console.log('curl -X GET ' + url + '/google-home-notifier?text=Hello+Google+Home');
	console.log('POST example:');
	console.log('curl -X POST -d "text=Hello Google Home" ' + url + '/google-home-notifier');
    // sheetの一番左上のCellを取得
    sheet.getCells({
       'min-row': 1,
       'max-row': 1,
       'min-col': 1,
       'max-col': 1,
       'return-empty': true
    }, function(error, cells) {
          var cell = cells[0];
          cell.value = url + '/google-home-notifier'; //アクセスしてほしいURLをセット
          cell.save(); //保存
          console.log('spread sheet update successful!!');
    }); 
  });
})
```

修正したら保存。foroeverが動いていれば自動で再起動がかかってくれるはず。かかってくれなかったらstop->startなど。

うまくいけばシートの左上のセルにURLがぶっこまれる。
あとはこのセルを見るようにしてあげれば常に最新のURLが参照できるようになる

# トラブルシューティング
なんか動かないんだけど！って時に

## 8時間でURLが失効しないようにする
[google-home-notifierが気づいたら動かなくなってた時の対処法](https://qiita.com/k_keisuke/items/2974ddaf2bf24a3ea32e)
詳細は上記の記事にて。
この対応をしないと**ngrokの取得したURLが8時間で失効してしまい、GoogleHomeがレスポンスを受け取れなくなってしまう**。なので、ほぼ必須。

## google-tts-apiを更新する
[google-home-notifierが気づいたら動かなくなってた時の対処法](https://qiita.com/k_keisuke/items/2974ddaf2bf24a3ea32e)
詳細は上記の記事にて。
この対応をしないと**そもそもgoogle-home-notifierが動かない**。一応[PR](https://github.com/noelportugal/google-home-notifier/pull/47)があるけどまだマージされてないので対応は必須になる。

# おまけ(GASからのアクセス例)
スプレッドシートと相性がいいのはやはりGAS(Google Apps Script)。
IFTTTからのWebhookも受けられるし、スクリプトの定期実行もできるのでおおよそやりたい放題できる。
なので、GASからgoogle-home-notifierへメッセージを飛ばすサンプルコードを載せておく

```js:google-home-notifier-example.gs
function getNgrokUrl() {
  if (getNgrokUrl.instance) { return getNgrokUrl.instance; }
  var ngrokSheetId = "xxxxxxxxxxxxxxx"; //スプレッドシートのId
  var url = SpreadsheetApp.openById(ngrokSheetId).getSheetByName("yyyyy").getDataRange().getValues()[0][0]; //yyyyyはワークシート名
  return url;
}

function notify2GoogleHome(msg) {
  // msg = "テストメッセージ"; //debug用
  sendHttpPost(msg, getNgrokUrl());
}

/* 
* messageをpostする関数
*/
function sendHttpPost(msg, url){
   var payload =
   {
     "text" : msg
   };
//   var userid = "user"; //Basic認証をかけている場合にこの行を有効にする
//   var password = "password";  //Basic認証をかけている場合にこの行を有効にする
   var options =
   {
     "method" : "post",
// "headers" : {"Authorization" : " Basic " + Utilities.base64Encode(userid + ":" + password)}, //Basic認証をかけている場合にこの行を有効にする
     "payload" : payload
   };

   UrlFetchApp.fetch(url, options);
}
```

# 終わりに
スマートスピーカーアドベントカレンダー間に合わなかった...