GoogleHomeからTwitterの最新ツイートを喋らせる(RaspberryPi + Node-Red)

※2020/9/30 おまけを別記事へ移動

気づいたら3年目に突入しました。
ここ1年以上、プログラマーとは？という業務をしているため、
業務と全く関係ない開発をしてみました。リハビリです。

前々から、スマートスピーカーで遊びたい…Twitterで呟かれる特定アカウントの最新情報に気が付きたい…
という思いがあったため作成しました。

Node-Redは今回はじめて触ったのですが、わかりやすいUIで楽しいですね！！

TwiiterAPIには制限があるため、利用する際には注意してください。

参考にさせていただいたリンクを巡りつつ、ツギハギしていただければ作成できます。
※サンプルコードも載せています。

# ■ 利用機材
* Raspberry Pi3
* Node-Red 1.0.6
* Google Home mini


# ■ 環境構築
Node-Redが利用できれば、サーバーレス等のクラウド環境でも問題ないかと思いますが、
家の中で放置状態になっていたRaspberry Pi3を利用し、
自宅LAN内環境での接続を行っています。
そのため、別環境でのNode-Redの稼働についてはわかりません。。。

主に環境構築時に利用したサイトは下記になります。
> 
1 : Raspberry Pi 初回設定（購入 + OSインストール + セキュリティ設定）
https://qiita.com/henjiganai/items/2b39dd8cbc3cc3cbe4a4
> 
2 : Raspberry Piで実行する : Node-RED日本ユーザ会
https://nodered.jp/docs/getting-started/raspberrypi


自宅LAN内環境ではIPの固定を忘れないようご注意ください。
※固定化したIPアドレスへ接続を行います。
　ルーターでの自動割当がされないIPへの固定をオススメいたします。
　IP固定については、
　192.168.XX.XXX　←極論、最後の3桁箇所を255未満の割り当てられない好きな数字にすれば大丈夫です。


# ■ GoogleHome /  mini / Nestについて
家の中で使えるよう、セットアップ等が済んでいれば問題ないかと思います。
一部で割り当てられているIPを利用するため、下記記事を参考に確認してください。

> Google Home IPアドレスの確認
https://qiita.com/nigo1973/items/15b403b7658d6276946f


# ■ とりあえず喋らせる編（Not Twitter, 手動喋らせチャレンジ）
下記サイトをご確認ください。

> 1 : Node-REDを使って簡単にGoogle Homeに喋らせよう
https://qiita.com/dozensofdars/items/6cec7db44636374b5382
> 
2 : Raspberry PiからGoogle Homeを喋らせる（Node-RED編）
https://qiita.com/ktetsuo/items/87b38a6b360cbdc51fc1

上記サイトはそれぞれ内容についてはほぼ同じです。
1では、喋らせるまでのフローが詳細に記載されています。
GoogleHomeのIPアドレスを直接「Castノード」で指定しています。
GoogleHomeはアプリからIPアドレスの固定はできないため、変わる可能性があります。

2では、GoogleHomeのIPアドレスを直接「Castノード」に入れない仕組みとなっています。
GoogleHomeから送られてくるMDNS discoveryのメッセージを受信して
flow変数のcastdeviceにGoogleHomeの名前とIPを格納する仕組みが記載されています。

1で喋らせるまでの流れを把握し、2の内容に付け替えることをオススメします。

サンプルは下記になります。

```json:とりあえず喋らせる編.json
[{"id":"6da63c45.5e1544","type":"tab","label":"とりあえず喋らせる編","disabled":false,"info":""},{"id":"2bf16aa0.81db66","type":"change","z":"6da63c45.5e1544","name":"出力変換","rules":[{"t":"set","p":"message","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":220,"wires":[["b6143535.4c5e38"]]},{"id":"34c6966d.406aca","type":"discovery","z":"6da63c45.5e1544","name":"","topic":"","service":"_googlecast._tcp","x":140,"y":100,"wires":[["214d14d3.fa60ec"]]},{"id":"7c0671c1.e4e3b","type":"debug","z":"6da63c45.5e1544","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":510,"y":100,"wires":[]},{"id":"214d14d3.fa60ec","type":"function","z":"6da63c45.5e1544","name":"add to flow.castdevice","func":"const name = msg.payload.txtRecord.fn;\nconst ip = msg.payload.addresses[0];\nmsg.name = name;\nmsg.ip = ip;\nvar castdevice = flow.get(\"castdevice\");\nif (castdevice === undefined) {\n   castdevice = {};\n}\ncastdevice[name] = ip;\nflow.set(\"castdevice\", castdevice);\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":100,"wires":[["7c0671c1.e4e3b"]]},{"id":"3734e4d5.c829ec","type":"comment","z":"6da63c45.5e1544","name":"GoogleHomeの名前とIPのペアをflow.castdeviceに入れる","info":"","x":250,"y":60,"wires":[]},{"id":"34d0838.4098c7c","type":"cast-to-client","z":"6da63c45.5e1544","name":"Cast端末","url":"","contentType":"","message":"","language":"","ip":"","port":"","volume":"","x":580,"y":220,"wires":[["a2480a2e.809348"]],"icon":"node-red-contrib-cast/google-home-mini2.svg"},{"id":"b6143535.4c5e38","type":"change","z":"6da63c45.5e1544","name":"ファミリールーム","rules":[{"t":"set","p":"ip","pt":"msg","to":"castdevice[\"ファミリー ルーム\"]","tot":"flow"},{"t":"set","p":"language","pt":"msg","to":"ja","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":220,"wires":[["34d0838.4098c7c"]]},{"id":"a2480a2e.809348","type":"debug","z":"6da63c45.5e1544","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"message","targetType":"msg","x":740,"y":220,"wires":[]},{"id":"853c160b.165368","type":"comment","z":"6da63c45.5e1544","name":"GoogleHomeを喋らせる","info":"","x":150,"y":160,"wires":[]},{"id":"715e3acd.c9a1e4","type":"inject","z":"6da63c45.5e1544","name":"通常","topic":"","payload":"これは通常つぶやきテストです。","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":220,"wires":[["2bf16aa0.81db66"]]}]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/c5d90f45-7df6-4526-1547-7e341f1533fa.png)

「ファミリールーム」ノードに記載されている「castdevice["ファミリー ルーム"]」の" "内を
手持ちの端末名に変え、「通常」の左横にあるボタン部分をクリックすることで、
「通常」ノードの中に記載されている文章を喋りだすはずです。
※添付画像はデプロイをしていないため非活性になっています。
　編集した後にはデプロイを行うことを忘れないでください。。

ちなみに、この「通常」ノードをTwitterAPIに書き換えると本記事の目的は達成されます。

# ■ TwitterAPIについて
TwitterAPIを利用する場合は、 デベロッパー申請が必要です。
私は下記サイトを参考に申請しました。
> 2020年度版 Twitter API利用申請の例文からAPIキーの取得まで詳しく解説
https://www.itti.jp/web-direction/how-to-apply-for-twitter-api/

私は英語力が皆無なので、DeepL翻訳様を頼りに文章を作成し、申請しましたが
ほぼ待たされることなく許諾を得ることができました。
申請文には文字数制限があるので、その点には注意してください。

# ■ Node-RedでTwitterAPIを使うための準備
Node-RedでTwitterAPIを利用するにはノードを利用する必要があるため、
下記サイトを参考にインストールしてください。

> node-red-node-twitter (node) - Node-RED
https://flows.nodered.org/node/node-red-node-twitter

インストールが完了すると下記ノードが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/38a54dde-aa07-afc0-c762-6ff3e4c9b69a.png)

今回利用するノードは「twitter in」になります。

フローに「twitter in」を追加し、編集画面を表示すると下記画像が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/2e83e751-1021-5dd1-7de7-5795b2fba270.png)
「新規に twitter-credentials を追加…」の部分にデベロッパー許諾を得たアカウントを連携する必要があります。
連携するために「新規に twitter-credentials を追加…」横にある鉛筆ボタンをクリックしてください。
下記画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/c89e2820-5e39-776f-986a-cab277290b4d.png)

「Twitter ID」欄にはデベロッパー申請許諾済アカウントのIDを記載してください。
1からTwitter社に利用目的を記載したアプリケーションを作成し、
2以降には作成後にもらえるkey, token 情報を記載してください。

画面としては下記を参考にしてください。
アプリケーションの「Keys and tokens」から同番号の内容をノードにコピーしてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/30c02e46-107f-3ce6-98fc-8b30941d4595.png)
記載完了後、右上の「追加」ボタンをクリックすることで連携完了となります。

# ■ Twitterの最新ツイートを喋らせる編

前述した連携済みの「twitter in」を編集します。
「twitter in」ノードでは検索対象機能として下記項目があります。

* 全ての公開ツイート
* フォローしているユーザの全てのツイート
* 特定ユーザのツイート
* ダイレクトメッセージ

検索対象の中から、更に検索条件を元に取得したツイートを出力します。

今回の目的は、特定アカウントの最新情報となるため、「特定ユーザのツイート」を利用します。
サンプル画像は下記のとおりです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/8addcc42-5fc1-b676-67a6-149981df1d96.png)

そして、とりあえず喋らせる編で作った「通常」ノードを「twitter in」ノードに付け替えることで
Twitterの最新ツイートを喋らせることができます。

サンプルは下記になります。

```json:Twitterの最新ツイートを喋らせる編.json
[{"id":"a783959e.db9488","type":"tab","label":"Twitterの最新ツイートを喋らせる編","disabled":false,"info":""},{"id":"3dbb0c15.7ff184","type":"twitter in","z":"a783959e.db9488","twitter":"","tags":"@UN_NERV","user":"user","name":"","inputs":0,"x":130,"y":240,"wires":[["7c1ab09e.56e4d"]]},{"id":"1ebf8909.effb97","type":"debug","z":"a783959e.db9488","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"message","targetType":"msg","x":800,"y":240,"wires":[]},{"id":"182c8877.380d48","type":"cast-to-client","z":"a783959e.db9488","name":"Cast端末","url":"","contentType":"","message":"","language":"","ip":"","port":"","volume":"","x":640,"y":240,"wires":[["1ebf8909.effb97"]],"icon":"node-red-contrib-cast/google-home-mini2.svg"},{"id":"7c1ab09e.56e4d","type":"change","z":"a783959e.db9488","name":"出力変換","rules":[{"t":"set","p":"message","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":240,"wires":[["9c3f3661.7ff3f8"]]},{"id":"b997f246.5e87c","type":"discovery","z":"a783959e.db9488","name":"","topic":"","service":"_googlecast._tcp","x":140,"y":100,"wires":[["ca1e3ce8.5abff"]]},{"id":"79bfeb2.12aa414","type":"debug","z":"a783959e.db9488","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":510,"y":100,"wires":[]},{"id":"ca1e3ce8.5abff","type":"function","z":"a783959e.db9488","name":"add to flow.castdevice","func":"const name = msg.payload.txtRecord.fn;\nconst ip = msg.payload.addresses[0];\nmsg.name = name;\nmsg.ip = ip;\nvar castdevice = flow.get(\"castdevice\");\nif (castdevice === undefined) {\n   castdevice = {};\n}\ncastdevice[name] = ip;\nflow.set(\"castdevice\", castdevice);\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":100,"wires":[["79bfeb2.12aa414"]]},{"id":"66af4556.ef14ac","type":"comment","z":"a783959e.db9488","name":"GoogleHomeの名前とIPのペアをflow.castdeviceに入れる","info":"","x":250,"y":60,"wires":[]},{"id":"9c3f3661.7ff3f8","type":"change","z":"a783959e.db9488","name":"ファミリー ルーム","rules":[{"t":"set","p":"ip","pt":"msg","to":"castdevice[\"ファミリー ルーム\"]","tot":"flow"},{"t":"set","p":"language","pt":"msg","to":"ja","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":240,"wires":[["182c8877.380d48"]]},{"id":"aba52f65.11a4","type":"comment","z":"a783959e.db9488","name":"稼働中システム","info":"","x":120,"y":180,"wires":[]}]
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320059/eb6540b0-195c-2825-49b8-0235106c8dab.png)

# ■ おわりに
元記事/サイトの皆様ありがとうございます。
おまけとして、追加要素を記載しています。
これらもサンプルコードを元につないでいただければ活用いただけるかと思います。

この機能の問題点は、特定ユーザのつぶやきがトリガーとなるため、
在宅でのWeb会議中に喋り始める可能性があることです。
その点にはご注意いただければと存じます。

# ■ おまけ
「Twitterの最新ツイートを喋らせる編」のままでは、いくつかの課題点があります。
詳細は下記記事をご確認ください。
https://qiita.com/nyanko5656/items/d009f9fd7e1d07590100

---

以上となります。
適宜「Twitterの最新ツイートを喋らせる編」に組み込んでいただき、活用いただければと存じます。

Tweet取得のタイミングからか、最大1分程度ラグが発生するときがあります。
その点について解消方法をご存じの方がいらっしゃたらぜひ教えていただけますと幸いです。

それでは、良き生活を！