satococoa's blog

主にサーバーサイド、Web 系エンジニアのブログです。Go, Ruby, React, GCP, ...etc.

InstagramのReal-time APIを使ってみた

Tokyo Real-time Photosというアプリを公開しました。
Nekostagramに代表される、Instagram系サービスが人気です。やや波に乗り遅れた感はありますが、僕も何か作ってみようと思い立ちチャレンジしました。

これは何?

Instagramで東京付近の写真がアップされると、リアルタイムで画面に表示されるだけのアプリです。具体的には以下の青丸の範囲です。(未公開の管理側です。)
InstagramReal-timeAPIを使っています。

技術的には?

信頼のsinatra + Herokuで作っています。リアルタイムに写真が通知される部分はPusherで、Redisをキャッシュ + DB用途で使っています。
こういった外部APIを使った簡易的なサービスにはsinatraは最適ですね。

Real-time APIについて

まずInstagramのReal-time APIを僕が使った範囲でちょっと説明します。
利用手順としては3ステップになります。
  1. 条件を指定して購読申し込みを行う
  2. 購読申し込みに対してInstagramから確認を受ける
  3. Instagramからの通知を受け取る
それぞれコードを見ながらいきます。
1. 条件を指定して購読申し込みを行う
post '/admin/subscriptions' do
  require_admin # ここではとりあえず無視してOKです。
  client = Instagram.client(:access_token => session[:access_token])
  obj = client.create_subscription(:client_id => Instagram.options[:client_id],
                                   :client_secret => Instagram.options[:client_secret],
                                   :object => 'geography',
                                   :aspect => 'media',
                                   :lat => params[:lat],
                                   :lng => params[:lng],
                                   :radius => 5000,
                                   :callback_url => subscript_url)
  # subscript_urlは更新の通知を受け取るURLです。
  data = {:lat => params[:lat], :lng => params[:lng], :radius => 5000}
  REDIS.set("subscription:#{obj['object_id']}", data.to_json)
  # create_subscriptionの返り値からobject_idを受け取り、緯度・経度・半径の情報と一緒に保存
  obj
end
これは管理画面で地図上の点をクリックしたときにAjaxで呼び出されるコードです。
create_subscriptionメソッドを使って、クリックした点を中心とした半径5km圏内の更新に対して購読を申し込みます。するとInstagramから購読申し込みに対しての確認アクセスが来ますので、それに答えてあげるのが次のコードです。
2. 購読申し込みに対してInstagramから確認を受ける
get '/subscription/callback' do
  params[:'hub.challenge']
end
(本当はそのアクセスがほんとにInstagramから来ているのか、ちゃんと検証すべきでしょうけれども、やってません;;)
さきほど:callback_urlで指定したURLにInstagramからGETのアクセスが来ます。そしたらパラメータのhub.challengeをそのまま返してあげてください。これで購読完了です。

Herokuでやる方へ注意:この際、先ほどの購読申し込みのPOSTリクエストが、購読確認のGETリクエストに対してレスポンスを返すまで終了しません。つまり、同時に2アクセスは最低さばかないといけない、ということです。
仕方ないので、僕はこのときだけDynoを2個にしました。ハマりました。
3. Instagramからの通知を受け取る
post '/subscription/callback' do
  #略
end
略し過ぎでごめんなさい。でも普通にcallback_urlにしたURLにPOSTのリクエストが来るだけです。
ただ、ちょっと注意したいのがここで実際に写真データが送られてくるわけではないことです。

購読申し込みが成功すると、create_subscriptionメソッドは:object_idを含むオブジェクトを返してきます。
そして、実際に写真がUPされてアプリ側にInstagramから通知が来るときには、その:object_idを含むオブジェクトを送ってくるので、それをもとにInstagramからAPIを使って画像を持ってくる必要があります。
(・・・ですよね、多分。しっかり調べた訳じゃないのでちょっと自信がないのです。)

ということで、詳しくは github上のコードを参考にしてみてください。

最後に制限事項

Herokuからアドオンとして利用できるPusherを使っていますが、これが1日に1万回までしかリクエストを受け付けてくれません。
時間帯によって写真がアップされる頻度が違っているためによくわかりませんが、もしかすると簡単に1万回は超えてしまうかもしれません。
自動で写真が表示されなくなったら察してください。

時間帯によって、一定枚数の写真をキャッシュすればいいのかもしれませんが、とりあえずはInstagramからPOSTが来るたびにリアルタイムに通知していってみようかと思います。

以上です。お楽しみいただけると幸いです!