JavaScriptでInstagramのWeb UIを作る

Instagram APIで、それなりに色々なことができるなーと調べているうちに、「JavaScriptだけでiPhone Appと同等のページを作れるのでは?」と思い、試しにちょっと作ってみた。
http://www1216u.sakura.ne.jp/jstagram/
JSで作ったInstagramってことで"JStagram"。iPhoneから見ると結構近いUIになっているんじゃないかと。
startボタンを押すと認証ページに飛んで、OKして戻ってくると自分のフィード(自分がフォローしているユーザの投稿)が見えます。ユーザ名のリンクはそのユーザの投稿一覧になる。 というところまでで時間切れ。


ソースを見ていただければ分かる通り、htmlページ1枚で作っていて、サーバ側の処理など何もしていません。URLのpathは一つで、クエリパラメータとフラグメント識別子を判別して書き出す内容を切り替える、ということをしているだけ。jsonpAPIを叩いて、結果をもとにjQueryでDOM生成。
http://instagr.am/developer/manage/ でclientを作る際のCALLBACK URLと、認証ページにリダイレクトさせるときに渡すURLが等しればちゃんとaccess_tokenを取得できるようなので、こういう使い方で認証が必要なページもJavaScriptだけで作れてしまう。


ということはjsdo.itでも作れますね。ということで作ってみた。

JStagram - jsdo.it - share JavaScript, HTML5 and CSS

「ポピュラー」タブの部分とか、場所ごとの一覧、follower&followingの一覧とかもAPIからとれるようなので、誰かフォークして作ってみてくれないかなー。スタイル調整とかも今のところ適当だし。。


まぁ、実用的なところではlistagramとかI4PCとかステキなのがあるので、これはただの試みってことで。APIもいつ変更されるか分からないし。

Instagram API が面白いかも

これって実は結構前から使えてたんですかね…?
A More Open Platform: The Instagram API
http://instagr.am/developer/
とりあえずclientを申請してゴニョゴニョと触ってみました。

Instagram popular photos - jsdo.it - share JavaScript, HTML5 and CSS

client_idだけあればJavaScriptだけでも結構いろいろできるみたいで、面白いものができそうな気がします。
access_tokenが必要なAPIもありますが これもOAuth2.0で結構簡単に取得できる…のかな?

instagram api sample - jsdo.it - share JavaScript, HTML5 and CSS

あと気になるのはReal-time API
http://instagr.am/developer/realtime/
ここはまだ触れてないのでもうちょい調べつついろいろやってみます。

orion editorで同時編集してみる

node.js+socket.ioでcodemirror editorを同時編集してみる の、続き。
editorを、codemirrorからeclipse orionのブラウザクライアントeditorに乗り換えてみた。
Planet Eclipse


以下のURLで動いています(前回の更新して上書きした)
http://www1216u.sakura.ne.jp:3002/
2窓で開いて片方見ながら片方編集してみると面白いと思います。


今回はgoogle-diff-match-patchのライブラリをそのままクライアント側でもサーバー側でも使用している。
orion editorはcodemirrorと比べてどうか、というとよくわからないけど なんかキレイに出来てるなー、という印象。
とりあえずどちらでもある程度やりたいことは実現できそうな気はする(複数人同時編集は色々むずかしいことがわかった)。用途に合わせて選択すればいいかな。

node.js+socket.ioでcodemirror editorを同時編集してみる

node.jsでchatアプリっぽいもの作るメモ - すぎゃーんメモ
node.js+socket.ioでライブコーディング的なものを作るメモ - すぎゃーんメモ
node.js+socket.io+oauth+SessionWebSocketでログイン付きチャットを作るメモ - すぎゃーんメモ
に引き続き、第4弾。


js製editorで編集情報をSocket.IOで送りつつ、他から送られてきた編集情報を反映する、複数人同時編集エディター的なもの。
sample: http://www1216u.sakura.ne.jp:3002/
source: https://github.com/sugyan/node-editor


エディタはcodemirrorというものを持ってきて使用した。
CodeMirror
コードの内容変更のイベントが取れたり局所的な変更も可能で、plugin的にparserを定義することで多言語でのSyntax highlightが可能。良いエディタだと思う。


以前ライブコーディング的なものを作ったときは、textareaのvalueを定期的に全部送信してviewerに反映させていたけど、さすがに非効率な気がしたのローカルで変更があったときだけ差分を送るようにしてみた。
差分(diff)の抽出のためのjsライブラリは探してみたのだけどgoogle-diff-match-patchくらいしか見つからず。これはdiffを取ってpatchとして適用させたりできて便利なのだけど、どうも文字列と文字列の差分を取るもので、「何文字目を消して何文字目にコレを挿入すれば差分が埋まる」という形式のdiffしか取れないようだった。今回使っているcodemirrorは主に"行"単位での編集APIが提供されていて、"何文字目"のような編集がしにくい。欲しいのはdiffコマンドのような"行単位"の差分…。
ということで行単位のdiffをとるために、PerlのAlgorithm::Diffからdiffサブルーチンの処理を丸パクリしてjsで実装した。
Algorithm::Diff - search.cpan.org
各クライアントからは自分が編集した差分をサーバーに送り、node.jsサーバーではその差分をそのまま他のクライアントにbroadcastしつつ、メモリ上に持つマスタとしてのコードに差分を反映させておき、定期的に同期をとるために発信する。各クライアントは編集できる状態でありながらもサーバーから来る差分情報や同期のためのマスタコードを勝手に反映させていく。
という仕組み。

なんとなくこれでうまくいくかなーと思っていたのだけど、複数人が編集しているとたびたび差分を受けて反映、同期が行われ、その度にカーソルが末尾の方に強制移動されてしまう… このへんの制御の仕方は残念ながらよく分からなかった。日本語を打つときも変なタイミングで勝手に確定されてしまったりで非常に不便。
あとiPhoneからは閲覧はできるけど編集はできないようだ。


まぁ仕組みがある程度あればエディタは(編集時のイベントが取れて、jsから書き換えることができれば)何でも良いかなと思っているので、今日話題になったEclipse Orionのエディタを試してみようかな。
Planet Eclipse
http://0-9.sakura.ne.jp/pub/orion/samples/demo.html


あと複数人同時編集エディタは既にあるので そこまで頑張って再発明するほどのものじゃなかったり…
online text editor - collabedit

node.js+socket.io+oauth+SessionWebSocketでログイン付きチャットを作るメモ

(※2011/09月 追記: この記事の方法は既に古いので Socket.IOとHTTPセッションの共有は Socket.IO と Express でセッションの共有 - Block Rockin’ Codes などを参考にしましょう)


node.jsでchatアプリっぽいもの作るメモ - すぎゃーんメモ
node.js+socket.ioでライブコーディング的なものを作るメモ - すぎゃーんメモ
に引き続き、第3弾。


現在、サンプルを以下の場所で動かしています。
http://www1216u.sakura.ne.jp:3001/
ソースは GitHub - sugyan/node-oauth-chat: node.js + socket.io chat (using Twitter OAuth) に置いてあります。

セッション共有

Socket.IOが便利なのは分かったのだけど、HTTPでのログイン情報などsession情報を使いたいこともありますね。
Google グループ ではexpressのdynamicHelpersを使うととれるよ、みたいなことが書いてあるようなんですが、試してみたところ少なくとも僕の環境ではうまくいきませんでした。
ということでSessionWebSocketを使います。SessionWebSocketについては以下の記事がとても詳しいです。
Node.js 日本ユーザグループ Blog: HTTP と WebSocket でセッションを共有する
ここにも書いてある通り、どうも挙動がおかしいようで。少なくとも現在の0.1.1ではsecureイベントのあとmessageが渡りません。のでpull requestで放置されてしまっている(?)このパッチを入れて使いました。

仕組みとしては、connnectする前にAjaxでhttpリクエストを行い、サーバー側のミドルウェアでそのhttp requestからsessionを取得して管理するようにする、ということのようです。クライアント側では、

function SessionWebSocket(cb) {
    $.ajax({
        url: "/",
        dataType: "json",
        cache : false,
        beforeSend: function(xhr) {
            xhr.setRequestHeader("x-access-request-token", "simple");
        },
        success: function(data) {
            var socket = new io.Socket();
            socket.connect();
            socket.send(data["x-access-token"].split(";")[0]);
            cb(socket);
        }
    });
}

のようなものを用意しておき、

SessionWebSocket(function(socket) {
    socket.on('message', function(msg) {
        ...
    });
})

という形でコールバックを渡すようにして使います。
サーバー側は、expressなら

var express = require('express');
var app     = express.createServer();
var io      = require('socket.io');
var sws     = require('SessionWebSocket')();

app.configure(function() {
    app.use(express.cookieDecoder());
    app.use(express.session());
    app.use(sws.http);
});

...

var socket = io.listen(app);
socket.on('connection', sws.ws(
    function(client) {
        client.on('secure', function() {
            console.log(client.session);
        });
    }
));

というかたちで設定して使うことで、socket.ioのコールバックに渡されるclientからclient.sessionでhttpのrequest sessionにアクセスできるようになるみたいです。

ログイン

ログインの仕組みは色々な実装方法が考えられると思いますが、今回はTwitter OAuthを使う方法を試してみました。
npmに"connect-auth"というのがあってTwitterだけでなくGithubFacebookや色々なものに対応してログインする仕組みが用意されているようだったんですが、いまいち使いこなせずよく分からなかったので"oauth"だけつかって自前で実装することにしました。
oauthでのログインの実装は下記の記事がとても参考になりました。
node.js/express で CouchDB をパワーアップ大作戦 - Web屋の人の日記 || WebJourney 開発ログ

...
    var oauth = new (require('oauth').OAuth)(
        'https://api.twitter.com/oauth/request_token',
        'https://api.twitter.com/oauth/access_token',
        '***********************************', // consumer key
        '***********************************', // consumer secret
        '1.0',
        'http://localhost:3000/signin/twitter', // callback URL
        'HMAC-SHA1'
    );

    app.get('/signin/twitter', function(req, res) {
        var oauth_token    = req.query.oauth_token;
        var oauth_verifier = req.query.oauth_verifier;
        if (oauth_token && oauth_verifier && req.session.oauth) {
            oauth.getOAuthAccessToken(
                oauth_token, null, oauth_verifier,
                function(error, oauth_access_token, oauth_access_token_secret, results) {
                    if (error) {
                        res.send(error, 500);
                    } else {
                        req.session.user = results.screen_name;
                        res.redirect('/');
                    }
                }
            );
        } else {
            oauth.getOAuthRequestToken(function(error, oauth_token, oauth_token_secret, results) {
                if (error) {
                    res.send(error, 500);
                } else {
                    req.session.oauth = {
                        oauth_token: oauth_token,
                        oauth_token_secret: oauth_token_secret,
                        request_token_results: results
                    };
                    res.redirect('https://api.twitter.com/oauth/authorize?oauth_token=' + oauth_token);
                }
            });
        }
    });
...

普通のリクエストならtokenを生成して認証ページへリダイレクトさせてやり、callbackで返って来たときはoauth_token, oauth_verifierを使って認証処理を行う、正しくユーザー情報が取得できればsessionにセットして他のページに飛ばしてやる、という流れです。

まとめ

この2つを組み合わせて冒頭のサンプルを作りました。未ログインのユーザーはsocket.ioのsessionIdを、Twitterでログインしているユーザーはそのscreen_nameを使ってチャットに書き込めるようにしています。
チャットの仕組みは前々回書いた通り。今回はログをメモリ上に100件まで保持しておいてconnect時に一気に流すようにしてみています。

次に試したいこと

複数のSocket.IOチャネルを使うような方法がまだよくわかっていないので、例えば「ライブコーディングしながら 横ではテキストチャットも動いている」みたいなのをどう実装するのか調べて、やってみようと思います。

Kodをビルドしてみる

kodapp.com -  をちょっと使ってみようかと思いまして。
普通にバイナリ配布されてるからそれを使えば良いんだろうけど、特に意味もなくいちおう自前でビルドしてみた。
kod/README.md at master · rsms/kod · GitHub に書いてある通りにやっていくだけだけど。
homebrewとか使ったことなかったので今回はじめて入れてみた。

$ git clone git://github.com/rsms/kod.git
$ cd kod
$ ./pull.sh
$ ./deps/node-build.sh
$ ./deps/libcss/build.sh
$ ruby -e "$(curl -fsSLk https://gist.github.com/raw/323731/install_homebrew.rb)"
$ brew install source-highlight
$ ./deps/srchilight/import-from-homebrew.sh
$ xcodebuild
$ open build/Release/Kod.app

ビルドするのに少々時間はかかったけど、問題なくできた。と思う。
まだ使い方はよくわかってないのでこれから色々弄ってみる予定。

node.js+socket.ioでライブコーディング的なものを作るメモ

node.jsでchatアプリっぽいもの作るメモに引き続き、第2弾。
socket.ioを使って、あるtextareaで編集した内容を出来るだけリアルタイムに別のページに反映させるもの、を作ってみた。誰かがライブでtextareaで書いているコードをwebからみんなが見られる、的な。
https://github.com/sugyan/node-socket-sample にコード置いておきます。
サーバー側jsはこんなカンジで。

var express = require('express');
var app = express.createServer();
var ejs = require('ejs');
var io  = require('socket.io');

var port = 3000;
app.set('view engine', 'ejs');
app.set('view options', { layout: false });
app.set('views', __dirname + '/views');

app.get('/editor', function(req, res) {
    console.log('/editor');
    res.render('editor', { locals: { port: port } });
});
app.get('/viewer', function(req, res) {
    console.log('/viewer');
    res.render('viewer', { locals: { port: port } });
});
app.listen(port);

var socket = io.listen(app);
socket.on('connection', function(client) {
    client.on('message', function(msg) {
        client.broadcast(msg);
    });
    client.on('disconnect', function() {
        console.log('disconnect');
    });
});

console.log('Server running at http://127.0.0.1:' + port + '/');

editorページとviewerページを用意。httpのポートだけ任意に変えられるようにしています。
socket.ioは、「editorからのみmessageが来る」「viewerからはmessage受け取らずに送るだけ」という前提で、送信されてくるものをbroadcastするだけ。
で、"views/editor.ejs"

<html>
  <head>
    <title>editor</title>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">google.load("jquery", "1.4.4");</script>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script type="text/javascript">
$(function() {
    var socket = new io.Socket(null, { port: <%= port %> });
    socket.connect();
    socket.on('connect', function() {
        console.log('connect');
    });
    socket.on('disconnect', function(){
        console.log('disconnect');
    });

    var code_prev = $('#code').val();
    var loop = function() {
        var code = $('#code').val();
        if (code_prev != code) {
            socket.send(code);
            code_prev = code;
        }
        setTimeout(loop, 100);
    };
    loop();
});
    </script>
    <style type="text/css">
textarea#code {
    width: 500px;
    height: 200px;
}
    </style>
  </head>
  <body>
    <textarea id="code"></textarea>
  </body>
</html>

100msごとにtextareaの内容をチェックし、前回チェックしたときから変更されていればsocketにsendします。差分だけとりだして、とかやろうとすると面倒なのでとりあえず内容をすべて丸投げ。
"views/viewer.ejs"では

<html>
  <head>
    <title>viewer</title>
    <script type="text/javascript" src="https://www.google.com/jsapi"></script>
    <script type="text/javascript">google.load("jquery", "1.4.4");</script>
    <script type="text/javascript" src="/socket.io/socket.io.js"></script>
    <script type="text/javascript">
$(function() {
    var socket = new io.Socket(null, { port: <%= port %> });
    socket.connect();
    socket.on('connect', function() {
        console.log('connect');
    });
    socket.on('disconnect', function() {
        console.log('disconnect');
    });
    socket.on('message', function(msg) {
        $('#code').text(msg);
    });
});
    </script>
  </head>
  <body>
    <pre id="code" />
  </body>
</html>

socketから送られて来たものをそのまま反映するだけ。


これで、起動して片方では"/viewer"を開きつつ、別窓から"/editor"を開いてtextareaを編集すると…

結構リアルタイムに編集内容が反映されます。たのしい。
しばらくは
http://www1216u.sakura.ne.jp:3000/viewer
http://www1216u.sakura.ne.jp:3000/editor
で動かしておきますので壊さない程度に遊んでみて下さい。session管理とかしていないので色んな人がeditor側を弄ると大変なことになりそうだけど…w