Webサービス開発部の岡田です。ご存知の方は少ないかもしれませんが、ニフティでは「ネットとリアルをつなぐ」をコンセプトに、トークイベントを楽しめる飲食店「東京カルチャーカルチャー」を運営しています。
通称カルカルは開店から今年で10周年!そして、なんと12月7日にお台場から渋谷に移転します!
そんな新カルカルの隠れアトラクションとして、ネットとリアルをつないだ看板を作ってみましたのでご紹介します!
全体像
カルカルのfacebookページにいいね!が付くと、開発したWebアプリにfacebookサーバーからリクエストが送付され、それらを解析して該当アクションであれば、イベントハウス内に設置したWeMoにスイッチのOn/Offの命令を飛ばし看板がピカらせる構成になっています。
必要なもの
- WeMo
- WeMoアプリ経由で家庭内の電源を操作することが出来るプロダクトなのですが、ハックしてWebアプリケーションから操作できるようにします。
- ルーター&インターネット回線(今回はNifMoを利用しています)
- サーバー
Let’s Develop
Webアプリの開発
facebookAPPを作成するには事前にコールバックURLを作成しなければならないのでWebアプリから開発します。今回はgoのWebフレームワークであるginを利用しています。
すべて説明するのは長くなってしまうので、ポイントをいくつかピックアップしていきます。プログラムはGitHubに置いてあります。
GraphAPI(Webhooks)に登録するコールバックURLの準備
Graph API Webhooksのドキュメントに記載されている通り、コールバックURLはPOSTとGETの両方を用意する必要があります。GETリクエストはFacebook DevelopersでWebhooksを登録した際にコールされ、リクエストパラメータを解析して、以下の処理を行わなければ有効化されません。
1. パラメータ「hub.verify_token」にセットされてる値が、自身がFacebook Developersで入力した値か確認する
2. 1が確認できたら、「hub.challenge」にセットされている値のみレンダリングする
POSTについてはボディに必要情報が入っているので解析してコントロールしてあげる処理を記載します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/** * Webhookの受け口 */ router.POST("/facebook", func(c *gin.Context) { glog.Info("facebook POST call back was received.") go c.String(http.StatusOK, "") bufbody := new(bytes.Buffer) bufbody.ReadFrom(c.Request.Body) li.IlluminateCtrl(bufbody.Bytes(), controllers.FACE_BOOK) }) /** * Webhookの登録時のチェック用受け口 */ router.GET("/facebook", func(c *gin.Context) { if c.Query("hub.mode") == "subscribe" && c.Query("hub.verify_token") == config.FacebookInfo.VerifyToken { glog.Info("facebook Webhook subscribe.") c.String(http.StatusOK, c.Query("hub.challenge")) } else { c.String(http.StatusBadRequest, "UnKnown Rquest") glog.Warning("UnKnown Request was received") } }) |
WeMoの操作
WeMoの操作はSOAPで行われています。モバイルルーターでWeMoのMacアドレスを元に固定IPを割り当て、 http://<WeMoに割り当てたローカルIP>:49153/setup.xml にアクセスすると定義済みのアクションが確認できます。電源のスイッチ関連のアクションは以下です。
1 2 3 4 5 6 7 |
<service> <serviceType>urn:Belkin:service:basicevent:1</serviceType> <serviceId>urn:Belkin:serviceId:basicevent1</serviceId> <controlURL>/upnp/control/basicevent1</controlURL> <eventSubURL>/upnp/event/basicevent1</eventSubURL> <SCPDURL>/eventservice.xml</SCPDURL> </service> |
コントロールする際のURLは/upnp/control/basicevent1になりますが、アクションの詳細についてはhttp://<WeMoに割り当てたローカルIP>:49153/eventservice.xmlにアクセスして確認します。
疎通テストとして、ブラウザの拡張機能等を使ってWeMoに命令を送ってみます。
電源の状態を取得する
- リクエストURL
- http://WeMoのIP:49153/upnp/control/basicevent1
- header
- SOAPACTION : “urn:Belkin:service:basicevent:1#GetBinaryState”
- Content-Type : text/xml;charset=”utf-8″
- body
1234567<?xml version='1.0' encoding='utf-8'?><s:envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/"><s:body><u:getbinarystate xmlns:u="urn:Belkin:service:basicevent:1"></u:getbinarystate></s:body></s:envelope> - レスポンス
123456789<s:envelope s:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/"><s:body>https://engineering.nifty.co.jp/wp-admin/post.php?post=114&action=edit#<u:getbinarystateresponse><binarystate>0</binarystate></u:getbinarystateresponse></s:body></s:envelope>
電源をONにする
On/OffはBinaryStateにセットする値を1,0に変えることで可能です。
- リクエストURL
- http://WeMoのIP:49153/upnp/control/basicevent1
- header
- SOAPACTION : “urn:Belkin:service:basicevent:1#SetBinaryState”
- Content-Type : text/xml;charset=”utf-8″
- body
12345678910<?xml version='1.0' encoding='utf-8'?><s:envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/"><s:body><u:setbinarystate xmlns:u="urn:Belkin:service:basicevent:1"><binarystate>1</binarystate></u:setbinarystate></s:body></s:envelope>
WebアプリにWeMo操作を組み込む
ページに「いいね!」等が付いた際にwebhooksでPOSTされるデータは大体以下のような感じです
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{ "entry": [{ "changes": [{ "field": "feed", "value": { "item": "like", "verb": "add", "user_id": 1111111111 } }], "id": "1111111111", "time": 1478439821 }], "object": "page" } |
ドキュメントに記載されていないので試しながら法則性にアテをつけてモデルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package models import "fmt" func NewfacebookReq() facebookReq { return facebookReq{} } type facebookReq struct { Entry []Entry `json:"entry"` Object string `json:"object"` } type Entry struct { Changes []Change `json:"changes"` Id string `json:"id"` Time int `json:"time"` } type Change struct { Field string `json:"field"` Value Value `json:"value"` } type Value struct { Item string `json:"item"` Verb string `json:"verb"` User_id int `json:"user_id"` } |
POSTされてきたボディテキストを上記のモデルにUnmarshalしたのち「いいね!」の追加イベントか検査します。条件分岐としては、Value.Itemが”like”かつ、Value.Verbが”add”であれば追加イベントとして判定できそうなので・・・
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
func isFBIlluminateAction(bytefbJson []byte) bool { fbjson := models.NewfacebookReq() err := json.Unmarshal(bytefbJson, &fbjson) if err != nil { log.Println(err) } else { for _, e := range fbjson.Entry { for _, c := range e.Changes { if c.Value.Item == "reaction" || c.Value.Item == "like" || c.Value.Item == "comment" { if c.Value.Verb == "add" { return true } } } } } return false } |
として、このメソッドがtrueを返した際に、templateパッケージを利用して電源操作用のXMLを作成し、WeMoに送付します。
※ Onにしたいときは、{{.State}}に1をセット、Offにしたいときは0をセットします。
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:SetBinaryState xmlns:u="urn:Belkin:service:basicevent:1"> <BinaryState>{{.State}}</BinaryState> </u:SetBinaryState> </s:Body> </s:Envelope> |
facebook APPの作成
アプリの作成
1. https://developers.facebook.comにアクセスしてアプリを作成してくださ
2. webhook機能の有効化
プロダクトにwebhookを追加して、作成したWebアプリのコールバックURLと認証用のverify_tokenを設定します。ここで設定したtokenがwebhook登録時にGETリクエストとしてhub.verify_tokenパラメータの値でセットされてきます。また、「入力欄」はfeedを選択すれば、指定ページの「いいね!」やリアクションが取れます
3. アプリの公開
webhookはAPPが非公開であると利用できないです。
4. 監視対象のページをwebhookに登録する
Graph ExplorerをPOSTに変更してリクエストするのが一番簡単です。リクエストフォーマットに習い、ページをアプリに登録します。ただし、ページを登録する際は、そのページの管理者権限が必要です。
リクエストURL : /<監視対象ページID>/subscribed_apps
パラメータ : object: page, callback_url:webhookの受けURL, fields : [feed], active : true
その他の設定
今回はStickPCでちょっとした仕組みを入れていますが、電源のコントロールを完全に外部に出しても良い!!という場合はルーター側でポートフォワーディングさせルーターのグルーバルIPアドレスの49153ポートへのアクセスをWeMoのローカルIPアドレスに流せば一通りの連携が出来ると思います。ただし、ルーターが再起動するとIPは変わるので要注意です。
終わりに
渋谷でのリニューアルオープンは12月7日からです!
カルカルにお越しの際はぜひ、カルカルページに「いいね!」してピカらせてください!