The jonki

呼ばれて飛び出てじょじょじょじょーんき

Web版TweetDeckにFoursquareのチェックイン履歴を表示してみた

人気のTwitterクライアントTweetDeckFoursquareのチェックイン履歴を表示してみました。

f:id:jonki:20140525013950p:plain


きっかけ

なんでこんなことやってるのかというと下記ブログでFoursquareAPIの存在を知り、せっかくなので自分もやってみたかったのです。


割りとTwitter廃人のため、オリジナルのWebアプリを作るよりはよく使うTweetDeckのカラムに友人や自分のチェックイン情報が流れてきたら面白いんじゃね?と思ったのがきっかけです。
最初にネタバレするとこれはできませんでした(え)。が、FoursquareChrome 拡張を使ってそこそこ時間がかかったので記事にします。

構成

TweetDeckのWebページに対してChrome Extensionsを使ってHTMLの改ざんを行います。上の画像で分かる通りカラムを真似てFoursquareのチェックイン情報をアイテムとして表示しています。
Chrome ExtensionsはChrome拡張のことでHTMLとJavascriptの知識があれば簡単に作れる便利なものです。

foursquare API

TwitterなどのOAuthによるアプリを作ったことがある人はあまり苦労しないと思います。まずはアプリを作り、Client IDなどを生成しましょう。

次にこのアプリを使うユーザーに権限を委譲してもらいアクセストークンを生成してもらいます。下記のToken Flowの1と2を実行しましょう。

アクセストークンができたら試しに権限を委譲してもらったアカウントのチェックインリストをjsonでもらいます。vはバージョニングでこの日付のfoursquareAPI仕様を利用するというものです。afterTimestampはこの日付以降のもの、ということです。これはUNIXエポックと呼ばれる時間形式で1970年1月1日からの経過時間を示しています。オンラインで見やすい形式に変換するものもあります。テストではcurlでアクセスしてますが、もちろんブラウザで直接開いてもOKです。

$curl https://api.foursquare.com/v2/users/self/checkins?oauth_token=YOUR_ACCESS_TOKEN&v=20140212&afterTimestamp=1279044824


ここで友人のチェックイン情報もuser_idを指定して取得しようとしたところnot-authorizedとでてしまいました。どうやらこのAPIではまだUSER_IDはselfしか対応してないとのこと。がっくり。友人のリストは取得できます。(後述)

jsonのデータ構造ですが、下記のようにきれいに整形してくれるサービスを利用すると目的の値にどのようにたどっていけばわかりやすいです。

manifest.json

Chrome extensionsで使うmanifest.jsonになります。Chrome拡張でやらないよ、という人は見なくてもOKです。contentscript.jsが今回のメインのJavaScriptになります。

 {
   "name": "DeckSquare",
   "version": "1.0",
   "manifest_version": 2,
   "description": "DeckSquare",
   "content_scripts": [
   {
     "run_at": "document_end",
     "matches": ["http://*/*", "https://*/*"],
     "js": ["js/jquery-1.7.2.min.js", "js/contentscript.js"]
   }
   ],
   "permissions": [
     "tabs",
     "http://*/",
     "https://*/",
   ]
 }
contentscript.js

今回のメインコードになります。開いたページのタイトルがTweetDeckであった場合、チェックインリストを受け取って無理やりTweetDeckに突っ込んでます。データを取得するところまではいいと思いますが、やはり無理やり突っ込むのは無理がありそうですね…。自分でcssを書けば良いのですが、今回は面倒なのでTweetDeckのクラスなどをそのまま使わせてもらいました。汚くて読めたものではないですね。

var fromDay = 7;
var today = new Date();
var afterTimestamp = today.setDate(today.getDate() - fromDay) - today.getTimezoneOffset() * 60 * 1000;

if (document.title.indexOf('TweetDeck') != -1) {
  window.setTimeout('GetCheckinData()', 3000);
}

function GetCheckinData() {
  $.getJSON(
      'https://api.foursquare.com/v2/users/self/checkins?',
      {
        'oauth_token': 'YOUR_ACCESS_TOKEN',
        'v': '20140212',
        'afterTimestamp': afterTimestamp
      },
      function(data, status) {
        if(status === 'success') {
          var checkins = data.response.checkins.items;
          InsertFoursquareDate(checkins);
        }
      }
  );
}

function InsertFoursquareDate(checkins) {
    var columnParent = $('.js-app-columns.app-columns.horizontal-flow-container.without-tweet-drag-handles');

    var foursquareColumn = $('<section>', {class: 'js-column column  column-type-activity will-animate'})
                          .append($('<div>', {class: 'js-column-holder column-holder'}).add($('<div>', {class: 'column-panel'})));

    var contentContainer = $('<div>', {class: 'js-column-content column-content'});
    checkins.forEach(function(ck) {
      var venue = ck.venue;
      // convert epoch time to local time
      // a parameter for Date class is milliseconds.
      // createdAt[second], timezoneoffset[minute]
      var date = new Date((ck.createdAt + ck.timeZoneOffset * 60 ) * 1000).toLocaleString();
      var icon = venue.categories[0].icon;
      var iconUri = icon.prefix + '64' + icon.suffix;

      var article = $('<article>', {class: 'stream-item js-stream-item is-actionable'})
                    .append($('<div>', {class: 'js-stream-item-content item-box js-show-detail'}));

      var item = $('<div>', {class: 'js-tweet tweet'});
      var iconContainer = $('<div>', {class: 'obj-left item-img tweet-img', width: 48, height: 48}); // must set icon size. 32, 44, 64, and 88 are available

      var mapUrl = 'http://maps.google.co.jp/maps?q=' + encodeURI(venue.name) + '&=ll' + venue.location.lat + ',' + venue.location.lng + '&hl=ja';
      var mapDom = $('<p>').append($('<a>', {'href': mapUrl, 'text': 'Open Google map'}));
      var iconDom = $('<img/>', {class: 'tweet-avatar avatar pull-right', 'src': iconUri});
      iconContainer.append(iconDom);
      item.append(iconContainer);

      item.append($('<font>', {text: date, color: '#999999'})).append($('<br>'));
      item.append($('<font>', {text: venue.name}));
      item.append(mapDom);
      
      article.append(item);
      contentContainer.append(article);
    });

    foursquareColumn.append(contentContainer);
    columnParent.prepend(foursquareColumn);
}

最後に

本当はチェックインの通知もアプリで取得できるのですが、TweetDeckの改ざんが想定よりも筋が悪い気がしてきたので、このタスクは一旦止めちゃいました。ただ通知のフックやグローバルのストリーミングデータ(別途foursquareチームにメールが必要)もあるようなので、また別ネタでリトライ予定です。Chrome Extensionsで通知だけ、などでも良いかもしれないですね。

おまけ

ちなみに今回は使いませんでしたが、自分の友人の情報を取得するにはこんな感じでできました。

$.getJSON(
   'https://api.foursquare.com/v2/users/self/friends?',
   {
     'oauth_token': 'YOUR_ACCESS_TOKEN',
     'v': '20140212',
     'afterTimestamp': '1279044824'
   },
   function(data, status) {
     if(status === 'success') {
       var friendsData = data.response.friends.items;
       friendsData.forEach(function(friend) {
         var id = friend.id;
         var firstName = friend.firstName === undefined ? '' : friend.firstName;
         var lastName = friend.lastName === undefined ? '' : friend.lastName;
         var name = firstName + lastName;
         console.log(id + ': ' + name);
       });
     }
   }
);