※この記事は「enebular Advent Calendar 2016」12/15の分です。
初めて自分で「これ作ろう」と思い立って作ってみたものになります。
※構築過程を書きますが、説明が至らなかったり、間違った言葉を使っているところがありそうなので、どんどん突っ込んでくださると幸いです。
#レシピ
- enebular
- Google Cloud Platform
- Google Custom Search Engine
- Slack
これらを使って**「Google検索から画像を検索して、Slack Botから流す。」**というのを実装してみます。
記事の下の方にGoogleとSlackの設定を除いたコードを貼り付けています!
ポケモン生活を快適にする #とは
ポケモン最新作サン・ムーンにはQRスキャンという機能があります。
「QRスキャンで、新たなポケモンと出会おう!」
QRコードを3DSから撮影すると対応したポケモンが図鑑に登録されるという機能です
いまいちしっくりこない方は、バーコードバトラーのQRコード版だと考えてくださればおっけーです。(バーコードバトラーの知名度わからないけど)
いつでもQRスキャンできるわけではなく、スタミナのようなものがあり、時間で回復します。
10個スキャンするとポイントが溜まり珍しいポケモンに出会うことができるのですが、、毎回QRコードを用意するのがめんどくさい!
なので、ボタン一つでQRコードを投げてくれるSlack Botを作りました
Slack WebHook URL取得
とりあえず、functionの中のものをそのまま流す箱を作ります。 テストなのでFlowはシンプルなこんな感じに。 functionの中身。 ボタン押すとslack botから「てすと」と送信されます。SlackのWebhook URL取得手順
を参考にURLを発行します。
先ほどのFlowのinjectノードのボタンを押して発火すると
きました
なんか、無機質なのでSlackBot設定のページから設定します。
Google Custom Search EngineからJSONを受け取る
とりあえずGoogle Custom Searchで画像を取得してみる
google custom search engine(CSE)を使って、検索結果をjsonで取得する
こちらを参考にJSONを発行してみます。
Google Cloud PlatformやGoogle Custom Search Engineの設定などについては上記リンクからご覧ください。
これらから取得できるAPIを使用して話を進めていきます。
検索エンジンの権限を持ったAPIを発行して、それをカスタムサーチエンジンで使います。
https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM SEARCH ENGINE ID}&q={SEARCH_WORDS}
うまく日本圏内の結果が出ませんでしたが、検索ワードをうまく渡せてなかったようです。
https://www.googleapis.com/customsearch/v1?key={API_KEY}&cx={CUSTOM SEARCH ENGINE ID}&q={SEARCH_WORDS}&ie=UTF-8
ie=UTF-8
のパラメータを追加することで、日本語がしっかり検索に反映されたようです。(多分)
※追記:
少し検証を繰り返すとうまくいっていないことを確認しました。
(普通に検索バーから出した結果と比べると、QRコードがちがう。)
検索ワードを日本語直打ちのものをUTF-8に変換してから、SEARCH_WORDSのところに渡してあげることで解消されましたが、もっといい方法ないでしょうか・・・。
例)QRコード→QR%E3%82%B3%E3%83%BC%E3%83%89
※追記ここまで
SlackBotから送られてきた文字列
(以下オブジェクト型式でかなりつづく)
JSONは来ましたが、まだ生なので調理する必要があります。
enebularの本領発揮です。
その前に、QRコードのみ引っ張りたいので、検索オプションを追加しました。
tbs=ic:gray
を先ほどのURLパラメータに追加して、白黒の画像のみ検索結果に表示するようになりました。
私は検索結果のURLからtbs=ic:gray
やie=UTF-8
のパラメータをひっぱってきましたが、リファレンスに一覧がありましたので紹介しておきます。
https://developers.google.com/custom-search/json-api/v1/reference/cse/list
enebularでつなげる、整える。
JSONの中を見てみます。
{
~~~略~~~
"items":[{
"kind": "customsearch#result",
"title": "QR garchomp And ...",
"htmlTitle": "\u003cb\hoge.html",
"link": "http://www.garchomp.hoge/QR.png", //ここ画像
"displayLink": "www.garchomp.hoge",
"snippet": "strong dragon",
"htmlSnippet": "\u003cb\hoge\hoge",
"mime": "image/png",
"fileFormat": "Image Document",
"image": {
"contextLink": "http://www.garchomp.hoge/",
"height": 175,
"width": 175,
"byteSize": 411,
"thumbnailLink": "https://garchomp.img?q=tbn:ANxxxxxxxxxxxxxxxxxxxxxxxRGQ",
"thumbnailHeight": 100,
"thumbnailWidth": 100
}},{
"kind": "customsearch#result",
"title": "QR Dragonite And ..."
~~~略~~~
(中身はダミーです)
配列やオブジェクトで検索結果が並んでいて、link
のあとに画像URLがあることを拡張子を見て確認しました。
msg.payload
にjsonオブジェクトが入ってくるので、msg.payload = "msg.payload.items[i].link;
こんな感じに書けば、画像だけ取って来れますね。
そしたら、これをどうにかして10個抜き取ってしまえば良さそうです。
10個抜き取るFlow
とりあえず、受け取ったjsonを成型するために、
こんな感じに組んでみました。順番に説明していきます。
ループの実装
↑ 8の字を描いていていかにもループしていそうなところは、その通りループしています。iが10になるまでに流れ、同時にで変数の値を+1しています。
↓の中身
以下略
してますが、画像ごとにメッセージを変えたかったのでswich文使いました。
しかし、ここはenebularのswichを使うとfunctionを書かずに実装できますね。
変数iが足され、10になると最後に流れ、処理を終了します。「ループ後の処理」の中身はメッセージを書いているだけなので、割愛します。
ちなみに条件分岐しているの中身は以下になります。
左下の+ rule
を押すことで分岐先をさらに増やすことができます。
これでの左側をポチっと押すと検索結果の画像10件を取ってくることができます。
検索ワードをちょっとだけランダムにしてみる
話戻って、、今回はポケモン生活を快適にするということで、記事を書いています。
きっかけとなった「QRスキャン及び島スキャン」は、同じQRコードを2日連続で使用することができません。(一日置けば大丈夫です。)
なので、10個のQRのセットを検索ワードを変えることで4つ用意することにしました。
先ほどの(URLを渡す部分)にちょっと手を加えます。
最初のでは変数の初期化をしています。先ほどと一緒ですね。
で、その変数をランダムにしているのですが、一つのfunctionで良さそうですね。
中身はこんな感じです。
スマートにmsg.i = Math.random() * 4 | 0;
こう書いて、手前のノード消す方がよさそうです。
これで完成です。
の左側をポチっと押すと、4つの検索ワードからランダムに10件QRコードをゲットしてきます。
応用編?
検索のワードを変えれば・・・、そう、QRコード以外も検索できますね。
応用編として、flow変数などを使って、もう少しランダムに検索結果を取るようにしてみます。
それでは、現在推しているアイドルの画像を取ってきます
検索文字列を渡す前に、検索用の配列を作って固定文字+ランダム文字
の検索を行っています。
後半はloopではなく、ランダムに1つだけ取ってくるような設定をしているので、配列の個数*10通りの検索結果が期待できます。
↓
Slackがあのちゃんだらけになる!!!
(ノ・ω・)ノオオオォォォ-!!!
上のFlowでは「あのちゃん [配列内の文字列から1つ]」のように検索しています。
応用編、以上になります。
##Flowの共有
ポケモン生活より、ランダムに画像取ってくる方が需要があるのは確実なので、ソース↓
(※設定箇所が空欄で抜けていますので、Google周りやSlack周りを設定すれば好きな画像が飛んでくるぞ!)
[{"id":"54acff66.965d","type":"slack","z":"55626b8c.8b2f54","name":"","channelURL":"","username":"","emojiIcon":"","channel":"","x":701.5,"y":264.2222900390625,"wires":[]},{"id":"38284b91.517734","type":"template","z":"55626b8c.8b2f54","name":"+検索文字列","field":"payload","fieldType":"flow","format":"json","syntax":"mustache","template":"{\"word\":[\"dummy1\",\"dummy2\",\"dummy3\",\"日本語入力のものはUTF-8に書き換えてから配列に入れる\"]}\n","x":237,"y":97,"wires":[["d4be1101.91aed"]]},{"id":"8ac7a5ca.af98f8","type":"inject","z":"55626b8c.8b2f54","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"x":82,"y":97,"wires":[["38284b91.517734"]]},{"id":"d4be1101.91aed","type":"change","z":"55626b8c.8b2f54","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":290,"y":147,"wires":[["7253dfcd.e7c16"]]},{"id":"7253dfcd.e7c16","type":"json","z":"55626b8c.8b2f54","name":"","x":440,"y":145,"wires":[["bff0f422.3cafe8"]]},{"id":"bff0f422.3cafe8","type":"function","z":"55626b8c.8b2f54","name":"url","func":"var word = msg.payload.word[Math.random() * msg.payload.word.length | 0];\nmsg.url = `https://www.googleapis.com/customsearch/v1?key=[API_KEY]&cx=[CUSTOM SEARCH ENGINE ID]&searchType=image&q=[固定文字列]+${word}&lr=lang_ja&ie=UTF-8`;\n//[]←を書き換える\nreturn msg;","outputs":1,"noerr":0,"x":260,"y":203,"wires":[["ed9536bd.0bb548"]]},{"id":"ed9536bd.0bb548","type":"http request","z":"55626b8c.8b2f54","name":"","method":"GET","ret":"txt","url":"","x":463,"y":206,"wires":[["de50fee7.7f952"]]},{"id":"de50fee7.7f952","type":"json","z":"55626b8c.8b2f54","name":"","x":602,"y":207,"wires":[["acbfa189.93e27"]]},{"id":"acbfa189.93e27","type":"function","z":"55626b8c.8b2f54","name":"jsonから画像だけ","func":"var r = Math.random() * 10 | 0;\nmsg.payload = msg.payload.items[r].link;\n\nreturn msg;","outputs":"1","noerr":0,"x":541,"y":264,"wires":[["54acff66.965d"]]},{"id":"89b95091.3b1cc","type":"comment","z":"55626b8c.8b2f54","name":"検索ワードの配列","info":"","x":415,"y":93,"wires":[]},{"id":"a09d8c89.fcd7e","type":"comment","z":"55626b8c.8b2f54","name":"配列からランダムに検索ワードを追加する","info":"","x":229,"y":244,"wires":[]},{"id":"adbedf56.fa137","type":"comment","z":"55626b8c.8b2f54","name":"▼③WebhookURLをいれる","info":"","x":795,"y":225,"wires":[]},{"id":"ecfd563f.f33dc8","type":"comment","z":"55626b8c.8b2f54","name":"▲②CSE情報及びAPI情報をいれる","info":"固定検索文字列もここで入れる","x":293,"y":281,"wires":[]},{"id":"9500950f.f97c48","type":"comment","z":"55626b8c.8b2f54","name":"▼①ランダム検索ワードを入れる","info":"","x":317,"y":58,"wires":[]}]
importは、右上のメニューバーからClipboardを選択し、
#次回予告?
そして、そして、、
先日、弊社で@tseigoさんによるWio-Nodeハンズオンが行われ、参加いたしました。
(開催概要:IoTLT番外編! WioNodeハンズオン at ウフル)
(Wio-Nodeとenebularについてはこちら:enebularでデータを取りやすいWio Nodeへのシンプルな取得方法)
せっかく、Wio-Nodeが手元にあるので、の部分をセンサーにして、IoTチックにしてみたいと思います。
来週のAdventCalendarの記事でお披露目予定です!
flow変数を別件でごりごり使ったので、こちらの記事でflow変数やglobal変数について解説できればと思います。