tech.kayac.com

こんにちは。毎年花粉症になりかけている飯塚です。

最近のNode.jsの普及などによってサーバサイドJavaScript界隈が盛り上がっています。 そんなホットなJavaScriptをラクにかつエレガントに書くためのCoffeeScriptという言語をチュートリアル風に紹介します。

何かしらのライブラリを自分で書く程度にJavaScriptで開発している人は絶対に使ったほうがいいと思います。 JavaScriptを知っていれば1-2時間程度で十分駆使できるようになります。

(2011/6/28:@m_satyr様にご指摘頂いた箇所を修正しました。)

目次


CoffeeScriptとは?

  • JavaScriptにコンパイルできる小さな言語
  • コンパイル後はJavaScriptとして動作するため実行速度面ではほぼ変わらない
  • 構文がRubyやPythonライク
  • ブラウザ用の開発にもサーバサイドの開発にも使える

作者はDocumentCloudのJeremy Ashkenas氏です。 CoffeeScriptはSCSSなどと一緒にRails 3.1で公式にサポートされる予定です。

利点

  • タイプ数が減る
  • すでにJavaScriptを習得していれば学習コストが低い
  • 既存のJavaScript資産をそのまま使える

CoffeeScriptのソースはJavaScriptに比べて見た目がすっきりするため、クラス数の多くなる大規模開発では特に有効です。

欠点

  • コンパイルが必要

インストール

Node.jsの実行環境とnpm(パッケージマネージャ)が必要なので、入っていない場合はまずインストールします。 インストール方法はこちらの記事などを参考にしてください。

もしインストールせずに試してみたい場合、CoffeeScriptのサイトに行き、上部のメニューから「TRY COFFEESCRIPT」をクリックするとインタプリタが使えます。左のスペースにCoffeeScriptを入力すると右のスペースにJavaScriptが出てきます。

npmがインストールできたら以下のコマンドでCoffeeScriptをインストールします。

$ npm install coffee-script

Hello World

まずは Hello World を作ります。

hello = ->
    console.log("Hello World!")

hello()

上記のソースコードをtest.coffeeというファイル名で保存します。

それを以下のようにcoffeeコマンドでJavaScriptにコンパイルします。

$ coffee -c test.coffee

同じディレクトリにtest.jsが生成されますので、これをNode.jsで実行します。

$ node test.js
Hello World

test.jsの中身を覗くと以下のようになっています。

(function() {
  var hello;
  hello = function() {
    return console.log("Hello World!");
  };
  hello();
}).call(this);

綺麗にJavaScriptに変換されています。

また、test.jsを作らずにcoffeeコマンドで直接実行することもできます。

$ coffee test.coffee
Hello World

構文

Hello Worldスクリプトから見て取れるように、CoffeeScriptには以下の特徴があります。

  • var での変数宣言が不要
  • 関数の定義(function)の代わりに -> を使う
  • 式の終わりや行末にセミコロンを必ずしも置かなくてもよい
  • インデントでブロックを表現する

引数を取る関数の場合、() を省略して書くこともできます。

console.log("Hello World!")

console.log "Hello World!"

これらのコンパイル結果はどちらも同じです。

coffeeコマンドには主に以下のようなオプションがあります。

-c (--compile)

.coffee から .js にコンパイルします。

-b (--bare)

外側の (function(){ ... }).call(this); を取り除きます。

-w (--watch)

ソースファイルを監視して、ファイルが変更されるたびにコンパイルを実行します。

オプションなしでcoffeeコマンドを起動した場合、インタラクティブなインタプリタが起動します。

関数

-> を使って定義します。引数を取る場合は ->(var1, var2) -> のようにして引数を並べます。 関数内の最後の値が自動的にreturnされます。

CoffeeScript:

square = (x) -> x * x

↓コンパイル結果

square = function(x) {
  return x * x;
};

複数行にわたる場合はインデントを使います。

fib = (n) ->
    if n < 2
        n
    else
        fib(n - 1) + fib(n - 2)

1行で書くと以下のようになります。

fib = (n) -> if n < 2 then n else fib(n - 1) + fib(n - 2)

↓いずれもコンパイル結果は全く同じです:

var fib;
fib = function(n) {
  if (n < 2) {
    return n;
  } else {
    return fib(n - 1) + fib(n - 2);
  }
};

変数展開

" " でくくった文字列の中では #{変数名} で変数の値を埋め込むことができます。

CoffeeScript:

name = "Nao"
console.log "My name is #{name}!"

↓コンパイル結果

var name;
name = "Nao";
console.log("My name is " + name + "!");

演算子

JavaScriptの == は自動的に型変換が行われるため、意図しない結果となる場合があります。 CoffeeScriptでは == または is と書くとJavaScriptの === に変換されるため、型も含めた厳密な比較となります。

他にも and, or などを使って読みやすく書くことができます。対応は以下の通りです。

CoffeeScript | JavaScript
-------------+-----------
      is     |    ===
    isnt     |    !==
     not     |      !
     and     |     &&
      or     |     ||

Objectの生成

Objectを作るときは以下の書式が使えます。JavaScriptの { } も使えます。

また this.x の代わりに @x と書くことができます。

CoffeeScript:

pos =
    x: 100
    y: 200
    dump: ->
        console.log "x:#{@x}, y:#{@y}"

↓コンパイル結果

var pos;
pos = {
  x: 100,
  y: 200,
  dump: function() {
    return console.log("x:" + this.x + ", y:" + this.y);
  }
};

1行でも書けます。

CoffeeScript:

size = width: 100, height:100

↓コンパイル結果:

var size;
size = {
  width: 100,
  height: 100
};

この書式は関数の引数にも応用できます。

CoffeeScript:

myFunc(width:100, height:100)

↓コンパイル結果:

myFunc({
  width: 100,
  height: 100
});

ループ

配列

for .. in でループが書けます。

arr = ["a", "b", "c", "d", "e"]
for val in arr
    console.log val

ループ中に配列のインデックスを拾いたい場合はforinの間に2つ変数を置きます。

arr = ["a", "b", "c", "d", "e"]
for val, i in arr
    console.log "#{i}: #{val}"

連想配列 (Object)

of を使って配列と同じように反復処理ができます。

data =
    x: 100
    y: 200
for name, value of data
    console.log "#{name}: #{value}"

存在チェック

変数名や関数名の直後に ? を付けると「その変数あるいは関数が定義されておりnull以外の値が入っているかどうか」をテストできます。

CoffeeScript:

if myName?
    console.log "yes"
else
    console.log "no"

↓コンパイル結果:

if (typeof myName != "undefined" && myName !== null) {
  console.log("yes");
} else {
  console.log("no");
}

また .() の前に ? を置くことで、変数や関数が存在する場合のみ処理を進めるようにできます。

CoffeeScript:

console?.log?("Hello World")

↓コンパイル結果:

if (typeof console != "undefined" && console !== null) {
  if (typeof console.log == "function") {
    console.log("Hello World");
  }
}

ヒアドキュメント

''' でヒアドキュメントが使えます。行頭からのインデント分は自動的に削られます。

html = '''
       <html>
       <head>
        <title>CoffeeScript</title>
       </head>
       <body>
        <table>
         <tr>
          <td></td>
         </tr>
        </table>
       </body>
       </html>
       '''

↓コンパイル結果

var html;
html = '<html>\n<head>\n <title>CoffeeScript</title>\n</head>\n<body>\n <table>\n  <tr>\n   <td></td>\n  </tr>\n </table>\n</body>\n</html>';

thisのバインド

関数定義時に -> の代わりに => を使うと、thisがバインドされて外側のthisを参照するようになります。

CoffeeScript:

pos =
    x: 100
    y: 200
    dump: ->
        # 関数内部で@x(this.x)を使いたいのでfuncの定義は=>にしないといけない
        func = =>
            console.log "x:#{@x}, y:#{@y}"
        func()

↓コンパイル結果:

var pos;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
pos = {
  x: 100,
  y: 200,
  dump: function() {
    var func;
    func = __bind(function() {
      return console.log("x:" + this.x + ", y:" + this.y);
    }, this);
    return func();
  }
};

クラス

以下のようにしてクラスを使うことができます。

class Animal
    # newした時に呼ばれるコンストラクタ
    constructor: (name) ->
        @name = name
    say: (word) ->
        console.log "#{@name} said: #{word}"

class Dog extends Animal
    constructor: (name) ->
        # 親クラスのコンストラクタを呼ぶ
        super name
    say: (word) ->
        # 親クラスのメソッドを呼ぶ
        super "Bowwow, #{word}"

dog = new Dog("Bob")
dog.say("Hello!")

>> Bob said: Bowwow, Hello! と出力される

クラスの静的なプロパティを定義するには変数名の頭に@を付けます。

CoffeeScript:

class Dog
    @TYPE_CHIHUAHUA = 1

↓コンパイル結果:

var Dog;
Dog = (function() {
  function Dog() {}
  Dog.TYPE_CHIHUAHUA = 1;
  return Dog;
})();

無名関数

setTimeoutなど引数に無名関数を渡したい場合は、次の行頭にカンマを付けます。

CoffeeScript:

setTimeout ->
    console.log("ok")
, 1000

コンパイル後のJavaScript:

setTimeout(function() {
  return console.log("ok");
}, 1000);

(2011/4/22 追記) 1行で書きたい場合は以下のようにします。

setTimeout (-> console.log("ok")), 1000

{var}

nameという変数がある場合、{name} と書くと {name:name} の意味になります。

また = の左辺に {var} を書く記法があり、require()の際に便利です。

例えば便利なライブラリの一つにUnderscore.jsがありますが、これを使う場合、CoffeeScriptのファイルとunderscore.jsが同じディレクトリにあれば以下のようにしてロードできます。

{_} = require './underscore'

これは以下のコードと同等です。

_ = require('./underscore')._

なおUnderscore.js 1.1.3以降では ._ を付ける必要がなくなったため、以下のように読み込むことができます。

_ = require './underscore'

他にもまだまだあります

ほかにも便利な構文がいろいろあります。詳細はCoffeeScriptのサイトを参照してください。


Tips

実際に自分で使っていて気づいたtipsを紹介します。

?の使い分け

? 記号は三項演算子ではないため、意図したコードになっているか注意してください。

変数名や関数名の直後に ? を置いた場合は存在チェックになります。

一方、変数の後にスペースを入れて?を置くと、変数が未定義かnullの場合に特定の値を代入する式が書けます。

CoffeeScript:

root = exports ? window

↓コンパイル結果:

var root;
root = typeof exports != "undefined" && exports !== null ? exports : window;

?= を使って、変数が未定義またはnullの場合だけ代入することもできます。

CoffeeScript:

console ?= {}

↓コンパイル結果:

typeof console != "undefined" && console !== null ? console : console = {};

従来の三項演算子の用途には ? は使えないため、代わりにif/then/elseを使います。

result = if isTrue? then "true" else "false"

存在チェック漏れ

CoffeeScript:

if opts.key?
    console.log "yes"

と書いた際、opts.keyの存在はチェックされますが、opts変数の存在自体はチェックされません。 もしoptsがundefinedやnullなら実行時にエラーが出てしまいます。

↓コンパイル結果:

if (opts.key != null) {
  console.log("yes");
}

これに対処してoptsの存在もチェックするには以下のように書きます。

CoffeeScript:

if opts?.key?
    console.log "yes"

↓コンパイル結果:

if ((typeof opts != "undefined" && opts !== null ? opts.key : void 0) != null) {
  console.log("yes");
}

for文でのreturn値

for文の値を以下のように受け取ると、配列のmapに相当する処理が簡潔に書けます。

CoffeeScript:

values = (num * 2 for num in [1..5])
console.log values

>> [ 2, 4, 6, 8, 10 ] と出力される

↓ コンパイル結果:

var num, values;
values = (function() {
  var _results;
  _results = [];
  for (num = 1; num <= 5; num++) {
    _results.push(num * 2);
  }
  return _results;
})();
console.log(values);

関数では最後の値が自動的にreturnされますが、returnを書かずにforを関数の最後に置いた場合、配列を作ってreturnしようとしてしまうので気をつけてください。

例えば以下のようなケースです。

CoffeeScript:

myFunc = ->
    for i in [0..10]
        something()

↓コンパイル結果:

var myFunc;
myFunc = function() {
  var i, _results;
  _results = [];
  for (i = 0; i <= 10; i++) {
    _results.push(something());
  }
  return _results;
};

_resultsが生成されてreturnされています。

これを防ぐには明示的にreturnを書きます。

CoffeeScript:

myFunc = ->
    for i in [0..10]
        something()
    return

↓compile結果:

var myFunc;
myFunc = function() {
  var i;
  for (i = 0; i <= 10; i++) {
    something();
  }
};

isを使うときの型に注意

JavaScriptの==では自動的に型変換が行われますが、isでは型もチェックされますので注意してください。

例:

if 4 is "4"
    console.log true
else
    console.log false

>> false が出力される

if `4 == "4"` # ``で囲うと直接JavaScriptを書けます。
    console.log true
else
    console.log false

>> true が出力される

-wオプションでのコンパイルエラー

-w オプションを付けてcoffeeコマンドを起動しているとき、ソースを修正したつもりでもコンパイルエラーになっていてjsファイルが更新されていない時がありますので気をつけましょう。

MacであればコンパイルエラーをGrowlで通知してくれるJitterというツールもあります。

CoffeeScriptの直接実行

実はコンパイルをしなくても、ライブラリを読み込むことでブラウザ上でCoffeeScriptのコードを実行できます。

以下のようにjsdo.itでもCoffeeScriptが使えます。ぜひ Fork ボタンを押していじってみてください!

CoffeeScript出来るかな - jsdo.it - share JavaScript, HTML5 and CSS

ただしオーバーヘッドなども出てきますので、本番環境ではJavaScriptにコンパイルしてデプロイするようにしてください。

おわりに

以上、長くなりましたがCoffeeScriptについて紹介させていただきました。

カヤックでは新しい技術に興味のあるあなたのようなエンジニアを求めています!

参考URL

CoffeeScript (http://jashkenas.github.com/coffee-script/)

Underscore.js (http://documentcloud.github.com/underscore/)

Node.js (http://nodejs.jp/nodejs.org_ja/)

こんにちは。

カヤック自転車部盛り上げ担当でおなじみのaragaです。 自転車好きの方は是非一緒にヤビツ峠を登りましょう

今回は、いつか公開しようと2年間暖め続けていた、携帯サービスのFlashLite動的生成についてお話しします。

携帯サービスのFlash合成技術は、スマートフォンブームの普及により、この先の寿命は長くない技術かもしれません。しかし、モバゲーやGREEのガラケーソーシャルゲームの人気により現在進行形では最も熱い分野とも言えます。

今回はPHPライブラリの公開と、動的生成でできることの紹介をさせていただきます。

もし、携帯コンテンツのFlashで技術的に諦めて困っている人がいたら、少しでも参考になれば幸いです。

その前に、全ての機能を説明する為に、今回サンプルとして使用したサービスを簡単に紹介させてください。

ポケットフレンズコンチ

愛をモチーフにした可愛いペットのきせかえを軸に、多種多様のミニゲームで遊んだり、キノコを育てたり、 位置情報を使って道ばたに落ちているアイテムをバキュームで吸い込んだり、といった様々のコンテンツを全て可愛いペット「コンチ」と一緒に楽しむことができる超おススメの携帯ゲームです。

このゲームの大きなウリの1つに「うごき」があります。 ユーザーは50以上の「うごき」の中から好きなパターンから選んで最大4つの動きを組み合わせることができます。

うごきの動的生成は、モーションパーツのFlashのタイムラインを、ベースのFlashのタイムラインにコピーすることで実現させています。

サバイバルゲームタウン(サービス終了)

広いフィールドを移動し、銃で狙撃し合うFPS+RTSのような携帯ゲームで、コアなユーザー様には大変な評価いただきましたが、諸事情によりサービスは終了いたしました。

フィールドマップでは広いフィールドの中から常にプレイヤー周囲10x10のフィールドが見える状態で背景が変化していたのが特徴でした。

このゲームのフィールド画面では、MovieClipや画像の入れ替えはせずにテキストデータの入れ替えのみで様々なマップを生成しています

ボーリングパラダイス(モバゲータウンにて公開中)

ボウリングゲームでありながら、ピンをコレクションしたり、絶対に前を向かない後ろ向きのアバターを着せかえることができるゲームです。現在モバゲータウンにて公開されています。

このゲームのマイページで使われているFlashでは透過PNG、MovieClipの入れ替えを行っています。

FlashLite動的生成のツール

Flashの動的生成をするには用途に合わせて下記のようなツールを使います。

  • swfmill

    • ネット上でも非常に多くの情報が出ていますが、swf - xmlの双方向変換が可能なツールです。
    • xmlを編集しswfを生成するということが可能なため、swfのフォーマットを理解しXMLを操作できる技術者であればどんなものでも作ることができます。
    • しかし、swfをxmlに変換するためデータサイズが大きくなることやswfコマンドをたたく必要があるため負荷が高めになります。
  • SWFEditor

    • php拡張としてCで書かれているため非常に高速です。
    • phpでswfに画像を埋め込むなら圧倒的に早いです(当社比)。
    • しかし、様々な用途ひ合わせてカスタマイズをするにはややハードルが高い気がします。
  • Ming

    • PHPのPECLモジュールとして提供されています。
    • SWFに単純にswfを足し算するだけなら効果的な気がしますが、ドキュメントが充実していないので使い方がよくわかりません。
    • 純粋にswfを足し算するならswftoolsもおすすめです
  • バイナリを直接いじる

    • バイナリを直接操作するのも、状況によっては非常に効果的です。
    • 特にswfはフォーマットが公開されているので、バイナリ操作になれてしまえば簡単に扱うことができます

弊社の携帯チームでは、swfmillをつかった方法とバイナリを直接操作する方法を主に利用しています。

当初はswfmillのみだったのですが、変数を入れ替えるだけでswfmillを使うのは重すぎるといったこともあり、 バイナリを直接いじる手法を採用するようになりました。

SWFフォーマットについて

swfの動的生成について詳細を解説してもよいのですが、GREEのyoyaさんの記事よりわかりやすく解説する自信はないので、今回は参考の記事の紹介までとさせていただきます。

もし、とっても気になるようでしたら、僕に個人的に質問してください。とってもマニアックで楽しいひとときを一緒に過ごしましょう。

FlashLiteの動的生成をしてみよう

先に上げたような弊社サービスで利用しているPHPライブラリを公開します。

  • Media_SWF

    バイナリを直接いじるためのライブラリです。 IO_BitIO_SWFに依存しています (IO_Bitが無いと本当にめんどくさいです。)

  • swfmill

    swfmillを利用してswfの動的生成をします

今回、ライブラリの細かい紹介やリファレンスは割愛させていただきますが、 このシリーズにもし続きがあったらその時に詳細の説明をさせていただくかもしれません。

ちなみに公開したコードにはコメントなどはほとんどありませんし、大変お恥ずかしい部分も大量に残っています。 しかし、コードを整理してから公開すると言い続けて早2年が経過してしまったので諦めました。ご勘弁ください

また、次から上げていく機能については、両方に実装されているものもありますが、より高速なバイナリ版を優先して、バイナリ版で実装しているものはMedia_SWFで、swfmill版でしか実装していないものはswfmillを使ってサンプルを書いています

なお、実運用上はサンプルコードのままではなく、適宜MemcacheやAPCを用いて中間データをキャッシュしています。

ActionScript変数を入れ替える

URLや台詞、不正防止のトークン、ゲームのパラメータ等を埋め込むために最もよく使います。

// 元データ読み込み
$swf_data  = file_get_contents('path/to/base.swf');
$editor = new Media_SWF_Builder();
$editor->parse($swf_data);

// rootの最初のアクション部を取り出す
$firstAction = $editor->getFirstAction();
$firstAction->replaceValue('token', 'token'); // トークンを埋め込む
$firstAction->replaceValue('callback_url', 'http://example.com/index.php?hoge=fuga'); // コールバックのURLを指定

// 出力
echo $editor->build();

https://github.com/ken39arg/Media_SWF/blob/master/sample/100.php にサバイバルゲームタウンを例にしたサンプルを作成しました

JPEG画像を入れ替える

ユーザーが投稿した画像とFlashを合成する場合などに使います。

$swf_data  = file_get_contents('path/to/base.swf');
$editor = new Media_SWF_Builder();
$editor->parse($swf_data);

// インスタンス名でデータを取り出す為のデータを解析 
$editor->loadCharacterMap();

// BitmapID(ObjectID)を取り出す
$bitmap_id = $editor->getBitmapIdBySpriteName('/img');

// JPEGからSWFに埋め込むデータを作成
$image = new Media_SWF_Bitmap_JPEG('path/to/image.jpg');
$image->build();

// 生成したデータの埋め込み
$editor->setTagByCharacterId($bitmap_id, $image->getTag($bitmap_id));

echo $editor->build();

GIF画像を入れ替える

使い方はJPEGとほとんど変わりません。 PNGとの違いはSWFに変換する時にカラーパレットを利用しています

$image = new Media_SWF_Bitmap_GIF('path/to/image.gif');
$image->build();
$editor->setTagByCharacterId($bitmap_id, $image->getTag($bitmap_id));

PNG画像を入れ替える

GIFと違いカラーパレットを使用していません

$image = new Media_SWF_Bitmap_PNG('path/to/image.png');
$image->build();
$editor->setTagByCharacterId($bitmap_id, $image->getTag($bitmap_id));

MovieClipを入れ替える

MC及びその子のMCとShapeを再帰的に入れ替えます。

上記の画像もあらかじめMCに組み込んでおけばよいので最も自由度が高く調整しやすいです

$swf_data  = file_get_contents('path/to/base.swf');
$swf_data1 = file_get_contents('path/to/item_1.swf');

$editor = new Media_SWF_Builder();
$editor->parse($swf_data);
$editor->loadCharacterMap();

$item_editor1 = new Media_SWF_Builder();
$item_editor1->parse($swf_data1);
$item_editor1->loadCharacterMap();

$editor->changeSpriteBySpriteName($item_editor1, '/abatar/b/body'); // 上半身おなか
$editor->changeSpriteBySpriteName($item_editor1, '/abatar/b/left'); // 上半身左手
$editor->changeSpriteBySpriteName($item_editor1, '/abatar/b/right'); // 上半身右手

echo $editor->build();

https://github.com/ken39arg/Media_SWF/blob/master/sample/200.php にMCとPNGを入れ替えるボーリングパラダイスの例を作成しました

MovieClipの持つShapeの塗り色を変更する

最近はMovieClipで統一することが多いので、あまり使っていませんが、気軽にアイテムを増やすには色を変更するだけが一番お手頃です。

$swfmill_editor = new Swfmill('path/to/base.swf'); // Swfmill版でしか実装していません
$swfmill_editor->changeFillColor('avatar/h/skin', '#ff0000'); // 肌の色を赤に

echo $swfmill_editor->getSwfBinary();

モーションを入れ替える

タイムライン上のモーションを入れ替えます。 また、モーション素材の作り方には独自のルールをもうけています。

$swfmill_editor = new Swfmill('path/to/base.swf');
$motion_editor = new Swfmill('path/to/motion.swf');

// モーション同士(ラベルがモーション名)を入れ替える
$swfmill_editor->changeMotion('avatar', 'motion1', 'motion', $motion_editor);

// ラベル名を変更する
$swfmill_editor->changeMotionLabel('avatar', 'motion', 'motion1');

echo $swfmill_editor->getSwfBinary();

https://github.com/ken39arg/swfmill/blob/master/sample/300.php にコンチを例にしたサンプルを上げています

  • Media_SWFシリーズはparse()loadCharacterMap()は負荷が高いので実運用時は、必ず、serialize()したデータをキャッシュしてください。

さて、最初にも書きましたが、もし評判がよければ、次回以降「Flashデベロッパーとサーバーサイドエンジニアとのお約束ごと編」や「マップ生成のFlashLite実装編」「モーション入れ替え解説編」等を書いてみようかと思います。

あ、カヤックではいつでもエンジニアを募集しています!

すぐ近くの警察署の裏に防犯カメラ&屋根付きの私営の駐輪場があるので、自転車通勤でも安心です。

ついに21歳になりました!itaniです。

みなさん!!
つい先日、iPhoneで気軽に写真を共有できるサービス「instagram」のAPIが一般公開されましたね。
というわけで、さっそくOAuthを触ってみたので自分なりにまとめてみました。

なにができるの?

instagramAPIを使えば以下のようなことが簡単にできます。

  • ユーザー情報を取得
  • ユーザーの写真を取得
  • 写真に対してコメントやいいねを投稿する
  • フォローしている人、フォローされている人を取得

上で挙げたものはほんの一例です。
まだ調査不足ですが、リアルタイム系のAPIも用意されています。面白そうですよね。

OAuthってなんだ!?

最近twitterやfacebookでよく聞く『OAuth認証』という言葉。
果たしてこれはなんなのか。お願いしますgoogle先生。

OAuth (オー オース) は、ブレイン・クックとクリス・メッシーナが始めたオープンプロトコルであり、デスクトップ、モバイル、WebアプリケーションなどにセキュアなAPI認可 (authorization) の標準的手段を提供する。(Wikipediaより)

うむ。難しい。
ものすごく簡単にざっくり言うと、
「ある外部サービスのあなたの情報を使ってアプリケーションを作りたいから、APIを叩かせてもらえないでしょうか?どうぞよろしくお願いいたします。」
みたいな感じです。
この時、認証を外部サービスにゆだねることで、ユーザーはアプリケーション側にパスワードを教えることなく認可させることができます。
これがOAuth認証です!(どーん!)
ざっくりしすぎていてわからなかったそこのあなた!!こちらを参照してください。→ゼロから学ぶOAuth

さっそくさわってみよう!!

アプリケーションの登録

OAuthをするためには、外部サービスにアプリケーションの登録が必要です。
以下のリンクからinstagramの開発者登録を行い、アプリケーションを登録しましょう。

instagram developer

Instagram_01_o.png

赤線を引いた部分をクリックしてアプリケーションを登録します。
この時ログインを求められますが、すでにinstagramのアカウントを所持している方は、そのアカウントでログインできます。
まだinstagramのアカウントを持っていない人は、これを機会に新規登録を行いましょう!
どちらにしろ初めての開発者登録の場合は、以下のような開発者登録画面が表示されます。

Instagram_02_o.png

自分のwebサイトや電話番号など、必要事項を記入すれば開発者登録は完了です。
開発者登録が完了したら、左側のメニューにある「Manage」をクリックしましょう。
以下のようなページが表示されると思います。

Instagram_03_o.png

まだ開発者登録をしたばかりですので、当然登録されているアプリケーションの数は0個です。
では早速、アプリケーションを登録してみましょう!
Register a New Client!!

Instagram_04_o.png

以下の情報を記入するだけで、アプリケーション登録は完了です。

  • アプリケーション名
  • 説明文
  • websiteのURL
  • OAuth認証後のリダイレクトURI

今回はサンプル用に「instagram_api_test」というアプリケーションを一つ作ってみました。
登録が完了すると以下のように、Manageページにアプリケーションが表示されます。

Instagram_05_o.jpg

ここで重要になってくるのが、「CLIENT ID」「CLIENT SECRFET」この2つです。
これさえそろえばこっちのものです。OAuth認証を行う準備は整いました。

APIを叩くには??

「アプリケーションの登録も完了したし、さっそくAPIを叩いてあんなことやこんなことをやってみよー。ぐへへ」という不埒な妄想に浸っているそこのあなた!!そうは問屋が卸しません。
APIを叩くには、アプリケーションに対する、ユーザー固有のaccess_tokenが必要になります。

access_tokenの取得

access_tokenを取得するためにはユーザーに認証してもらう必要があります。
そう。ここで出てくるのがOAuth認証です!
この認証は上の方でも述べましたがinstagram側で行ってもらいます。
ですので、まずは認証のためのURLを取得してユーザーにはさっさとそっちに行ってもらいましょう。

認証URL

https://api.instagram.com/oauth/authorize/?client_id=CLIENT ID&redirect_uri=REDIRECT URI&response_type=code

認証URLはたったこれだけです。
client_idやredirect_uriの値は、アプリケーションを登録した際に発行したものを指定します。
twitterとかではredirect_uriはアプリケーション側で好きに設定できましたが、instagramはそうもいかないみたいです。
アプリケーションを登録した際のredirect_uri以外のuriを設定した場合、Exceptionが返ってきます。
正しいURLでアクセスした場合、以下のようなページに遷移します。

Instagram_06_o.png

Scope
認証URLを生成するときに、scopeを指定することができます。
scopeとは、このアプリケーションに対するAPIの許容範囲のことです。
現在、instagramAPIのscopeは以下の4種類があるそうです。

basic
基本的なユーザ情報へのアクセスができる。(フォロー一覧、写真一覧etc.)
comments
写真に対してコメントを投稿・削除できる
relationships
他人をフォローしたり、自分のフォロワーから外したりすることができる
likes
写真に対していいね!の投稿・削除ができる

デフォルトではbasicが指定されています。
とりあえず全部指定してみよう。ということで試していましたが意外なところで落とし穴がありました。
ここ重要です。テストに出ます。
instagramの公式ドキュメントには、複数指定する場合は「+」でつなげてパラメーターで渡す。と書いてありましたが、その通りにやるとExceptionが返ってきます。
いろいろ試してみた結果、「+」でつなげるのではなく半角スペースが正しい模様です。

すみません。盛大に勘違いしていました。
指摘をいただいたので調べてみたところ、scopeを指定している部分では、phpのhttp_build_query()を使っていたので半角スペースは「+」にエンコードされていました。
つまり、僕が失敗していた原因は、「+」文字をさらにエンコードしていたために、Exceptionが返ってきてしまっていたわけです。
instagramのドキュメントはこれっぽっちも間違っておりませんでした!!

以下がscopeを全部指定した場合のURLと認証画面です。

https://api.instagram.com/oauth/authorize/?client_id=CLIENT ID&redirect_uri=REDIRECT URI&response_type=code&scope=basic+relationships+comments+likes

Instagram_07_o.png

認証画面にてユーザーがYesをクリックすると、instagram側は設定されているredirect_uriに遷移しようとします。
この時instagramはredirect_uriにcodeというパラメータを付与してくれます。
このcodeを使用してアプリケーション側はaccess_tokenを取得します。

Redirect URI

http://your-redirect-uri?code=CODE
access_token取得URL
https://api.instagram.com/oauth/access_token
access_tokenを取得するためには以下のパラメーターが必要です。
client_id=CLIENT ID
client_secret=CLIENT SECRET
grant_type=authorization_code
redirect_uri=YOUR-REDIRECT-URI
code=CODE

requestMethodはPOSTを指定しましょう。GETで取得しようとすると怒られます。
正しく取得できた場合には以下のようなjsonデータが返ってきます。

{
        "access_token": "*********************************************", 
        "user": {
                "username": "w650", 
                "profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1370024_75sq_1296898384.jpg", 
                "id": "1370024", 
                "full_name": ""
        }
}

access_tokenと一緒に、ユーザー情報も返ってきました。嬉しい限りです。

これですべての準備が整った!!

ついに念願のaccess_tokenを手に入れました。
「これでAPI叩き放題だぜ。フゥワッフゥワッ!」と発狂しているそこのあなた!!

You are limited to 5000 requests per hour per access_token or client_id overall

なん・・・・だと・・・・。
どうやらinstagramは同じaccess_token、もしくは同じclient_idからのリクエストは、一時間の間に5000回までしか受け付けないそうですね。
access_tokenならまだしも、client_idで制限されちゃうのはつらいですね。
アプリケーションに一時間の間に5000人以上訪れたらアウトということでしょうか?
僕の翻訳間違いであってほしいです。

いざ!APIを叩いてみるべし!!

前置きがすごく長くなってしまいましたが、いよいよAPIを叩いてみようと思います。
instagramのAPIには大きく分けて以下のものに分類されるようです。

USERS
ユーザー関連のAPI
RELATIONSHIPS
フォロー、フォロワー関連のAPI
MEDIA
写真関連のAPI
COMMENTS
コメント関連のAPI
LIKES
いいね!関連のAPI
TAGS
タグ関連のAPI
LOCATIONS
位置情報関連のAPI

とはいえ、access_tokenを取得してしまえば、もう難しくはありません。
後はドキュメントに書いてある通りに、それぞれのAPIにaccess_tokenをパラメーターに含ませてやるだけです。

例えばこんな感じ。

https://api.instagram.com/v1/users/self/media/recent?access_token=**********************************

ドキュメントそのまんまですね。
これだけで自分の写真を新着順で取得できます。


stdClass Object
(
    [pagination] => stdClass Object
        (
            [next_url] => https://api.instagram.com/v1/users/1370024/media/recent?access_token=***********************&max_id=15127530
            [next_max_id] => 15127530
        )

    [meta] => stdClass Object
        (
            [code] => 200
        )

    [data] => Array
        (
            [0] => stdClass Object
                (
                    [tags] => Array
                        (
                        )

                    [type] => image
                    [location] => stdClass Object
                        (
                            [latitude] => 35.3209864
                            [id] => 66212
                            [longitude] => 139.5535169
                            [name] => 鎌倉どんぶりカフェ bowls
                        )

                    [comments] => stdClass Object
                        (
                            [count] => 0
                            [data] => Array
                                (
                                )

                        )

                    [filter] => X-Pro II
                    [created_time] => 1297424779
                    [link] => http://instagr.am/p/BhmsT/
                    [likes] => stdClass Object
                        (
                            [count] => 0
                            [data] => Array
                                (
                                )

                        )

                    [images] => stdClass Object
                        (
                            [low_resolution] => stdClass Object
                                (
                                    [url] => http://distillery.s3.amazonaws.com/media/2011/02/11/d4356b8de7c64ad09c09d9b7753d15ec_6.jpg
                                    [width] => 306
                                    [height] => 306
                                )

                            [thumbnail] => stdClass Object
                                (
                                    [url] => http://distillery.s3.amazonaws.com/media/2011/02/11/d4356b8de7c64ad09c09d9b7753d15ec_5.jpg
                                    [width] => 150
                                    [height] => 150
                                )

                            [standard_resolution] => stdClass Object
                                (
                                    [url] => http://distillery.s3.amazonaws.com/media/2011/02/11/d4356b8de7c64ad09c09d9b7753d15ec_7.jpg
                                    [width] => 612
                                    [height] => 612
                                )

                        )

                    [caption] => stdClass Object
                        (
                            [created_time] => 1297424782
                            [text] => 生姜焼きどーん!!
                            [from] => stdClass Object
                                (
                                    [username] => w650
                                    [profile_picture] => http://distillery.s3.amazonaws.com/profiles/profile_1370024_75sq_1296898384.jpg
                                    [id] => 1370024
                                    [full_name] => 
                                )

                            [id] => 30606942
                        )

                    [user_has_liked] => 
                    [id] => 25586451
                    [user] => stdClass Object
                        (
                            [username] => w650
                            [profile_picture] => http://distillery.s3.amazonaws.com/profiles/profile_1370024_75sq_1296898384.jpg
                            [id] => 1370024
                            [full_name] => 
                        )

                )

        )

)

一つの写真に対して、かなりの情報を返してくれますね。
位置情報や、いいね!やコメント。いろんなことができそうです。
next_urlにいたってはそのまま叩けば、2ページ目が取得できます。
簡単にページャが作れますね。至れり尽くせりです。

おわり

長くなっちゃったので、今日はここまでにします!
カッコいいViewerとか、位置情報入力して地図上に写真表示させるとか。なんかアプリケーション作ってみたいですね。
次はリアルタイム系のAPIに関してもっと調査してみようと思います!!

カヤックでは写真好きな技術者も募集しています!

追記

貴重な追加意見もいただいたので、追記しておきます。

  • クロール/キャッシュNG
  • アプリ名に「Instagram」は使えない

2011 3/2 Scopeの部分を修正しました。