Google Calendarの予定が始まる15分前にGoogle Homeから音声でリマインドを送る(2020年版)

Google Calendar のスケジュールを Google Home mini から通知させたいと思い、Developers.IO にブログを見つけたのですが、そのままでは色々動かなかったので、動くよう調整した内容を2020年版として共有します。
2020.05.11

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

クラスメソッドに入社以降 Google Calendar でのスケジュール共有が頻繁に行われている一方、在宅勤務により Google Home を活用する機会も多いので、何とか予定を目の前の Google Home mini から通知出来ないか調べたところ、稲葉さんのブログが見つかりました。

Google Calendarの予定が始まる15分前にGoogle Homeから音声でリマインドを送る

Developers.IO 何でもあるな…と驚きつつ試してみたところ、状況の変化によりそのままでは動作しないところがありましたので、2020年版の差分として共有します。

前提条件

  • Google Homeをセットアップ済みであること
  • Google Homeと同じLAN内にNode.jsが実行できるサーバがあること(所持している無印 Raspberry Pi には荷が重かったので、LANに繋がっている格安サーバを使用)

動作環境

  • Node.js:v12.16.3 (執筆時点のLTS)
  • npm:6.14.4
  • OS:CentOS 7.8.2003 (Core)

構成

オリジナルブログと同様のため省略。

やってみる

google-home-notifier が動かない

オリジナルの手順通り、Goole Home mini の IPアドレスを調べ、example.js を修正して実行しても動きません。

まず、ngrok によって自動生成される URL の FQDN が undefined となっています。

$ node example.js 
*** WARNING *** The program 'node' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=node>
*** WARNING *** The program 'node' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=node&f=DNSServiceRegister>
Endpoints:
    http://192.168.1.XXX:8091/google-home-notifier
    undefined/google-home-notifier
GET example:
curl -X GET undefined/google-home-notifier?text=Hello+Google+Home
POST example:
curl -X POST -d "text=Hello Google Home" undefined/google-home-notifier

とりあえずLANで、と example.js 実行マシンに対して curl を実行すると今度は以下のエラーとなります。

{ text: 'Hello Google Home' }
Error: get key failed from google
    at /home/XXXXX/google-home-notifier/node_modules/google-tts-api/lib/key.js:27:13
    at processTicksAndRejections (internal/process/task_queues.js:97:5)

ngrokの挙動を修正する

example.js を調べたところ、以下の箇所でエラーが発生し url が取得出来ていません。

  ngrok.connect(serverPort, function (err, url) {
Error: ngrok is not yet ready to start tunnels
    at Request._callback (/home/XXXXX/google-home-notifier/node_modules/ngrok/index.js:192:30)
    at Request.self.callback (/home/XXXXX/google-home-notifier/node_modules/request/request.js:185:22)
    at Request.emit (events.js:310:20)
    at Request.<anonymous> (/home/XXXXX/google-home-notifier/node_modules/request/request.js:1154:10)
    at Request.emit (events.js:310:20)
    at IncomingMessage.<anonymous> (/home/XXXXX/google-home-notifier/node_modules/request/request.js:1076:12)
    at Object.onceWrapper (events.js:416:28)
    at IncomingMessage.emit (events.js:322:22)
    at endReadableNT (_stream_readable.js:1187:12)
    at processTicksAndRejections (internal/process/task_queues.js:84:21) {
  error_code: 104,
  status_code: 503,
  msg: 'ngrok is not yet ready to start tunnels',
  details: {
    err: 'a successful ngrok tunnel session has not yet been established'
  }
}

どうしたもんかと思ったのですが、インストールされる ngrok のバージョンが 2.3.0 と古く、現在の ngrok は async/await に対応しているようなので、試しに package.json の ngrok を 3.2.7 に書き換えて、example.js を以下のように修正したところ、無事 url が取れるようになりました。

@@ -95,8 +95,8 @@ app.get('/google-home-notifier', function (req, res) {
   }
 })
 
-app.listen(serverPort, function () {
-  ngrok.connect(serverPort, function (err, url) {
+app.listen(serverPort, async function () {
+  const url = await ngrok.connect(serverPort);
     console.log('Endpoints:');
     console.log('    http://' + ip + ':' + serverPort + '/google-home-notifier');
     console.log('    ' + url + '/google-home-notifier');
@@ -104,5 +104,4 @@ app.listen(serverPort, function () {
     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');
-  });
 })
$ npm update ngrok
$ node example.js 
*** WARNING *** The program 'node' uses the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=node>
*** WARNING *** The program 'node' called 'DNSServiceRegister()' which is not supported (or only supported partially) in the Apple Bonjour compatibility layer of Avahi.
*** WARNING *** Please fix your application to use the native API of Avahi!
*** WARNING *** For more information see <http://0pointer.de/avahi-compat?s=libdns_sd&e=node&f=DNSServiceRegister>
Endpoints:
    http://192.168.1.XXX:8091/google-home-notifier
    https://取得したサブドメイン.ngrok.io/google-home-notifier
GET example:
curl -X GET https://取得したサブドメイン.ngrok.io/google-home-notifier?text=Hello+Google+Home
POST example:
curl -X POST -d "text=Hello Google Home" https://取得したサブドメイン.ngrok.io/google-home-notifier

Error: get key failed from google 問題を解決する

検索すると色々情報が出てきますが、現時点だと package.json の google-tts-api を 0.0.4 に書き換えれば良いようです。

Error: get key failed from google in version 0.0.3 · Issue #22 · zlargon/google-tts

$ npm update google-tts-api
$ curl -X GET http://localhost:8091/google-home-notifier?text=Hello+Google+Home
Google Home will say: Hello Google Home

サーバの用意

ここからは、基本的にオリジナルブログ通りで大丈夫です。

おそらくオリジナルブログを書かれた時点ではそのような仕様では無かったのだと思いますが、現時点だと作成したセッションは8時間で期限切れとなるようです。

いつの間にかアカウント登録無しでのngrok利用にセッション時間制限が設定されてた。 - ダンデライオン

google-home-notifierが気づいたら動かなくなってた時の対処法 - Qiita

ずっと動かし続ける必要があるサービスなので、事実上 Authtoken の取得が必須(ngrok へのアカウント登録が必要)となっています。

IFTTTの設定

IFTTT の画面は結構変わっていますが、設定内容は同じです。

最終的な差分

diff --git a/example.js b/example.js
index 43454e6..aa9e803 100644
--- a/example.js
+++ b/example.js
@@ -4,9 +4,12 @@ var ngrok = require('ngrok');
 var bodyParser = require('body-parser');
 var app = express();
 const serverPort = 8091; // default port
+const token = '取得したngrokのトークン';
+const user = 'Basic認証ユーザ名';
+const password = 'Basic認証パスワード';
 
 var deviceName = 'Google Home';
-var ip = '192.168.1.20'; // default IP
+var ip = 'Google Home mini のIP'; // default IP
 
 var urlencodedParser = bodyParser.urlencoded({ extended: false });
 
@@ -21,7 +24,7 @@ app.post('/google-home-notifier', urlencodedParser, function (req, res) {
      ip = req.query.ip;
   }
 
-  var language = 'pl'; // default language code
+  var language = 'ja'; // default language code
   if (req.query.language) {
     language;
   }
@@ -63,7 +66,7 @@ app.get('/google-home-notifier', function (req, res) {
      ip = req.query.ip;
   }
 
-  var language = 'pl'; // default language code
+  var language = 'ja'; // default language code
   if (req.query.language) {
     language;
   }
@@ -95,14 +98,13 @@ app.get('/google-home-notifier', function (req, res) {
   }
 })
 
-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');
+app.listen(serverPort, async function () {
+  const url = await ngrok.connect({authtoken: token, addr: serverPort, auth: user + ":" + password});
+  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');
-  });
 })
diff --git a/package.json b/package.json
index 4bb710e..651df71 100644
--- a/package.json
+++ b/package.json
@@ -17,8 +17,8 @@
     "body-parser": "^1.15.2",
     "castv2-client": "^1.1.2",
     "express": "^4.14.0",
-    "google-tts-api": "0.0.2",
+    "google-tts-api": "0.0.4",
     "mdns": "^2.3.3",
-    "ngrok": "^2.2.4"
+    "ngrok": "^3.2.7"
   }
 }

まとめ

作業に集中している時にも予定を知らせてくれるようになって、便利になりました。

google-home-notifier の更新が2年前から止まっているため、今後どこまで利用出来るのかという不安が無いわけではないですが、代替のものを探してくるのも難しそうなので、末永く動作して欲しいと思います。