解説

戻る

これは何?

これは論理思考とプログラミング~Webアーキテクチャを通して学ぶプログラムの基礎~の最終課題のサンプルで、Twitterとface.comのWebサービスAPIを組み合わせたWebサービスの一例です。

論理思考とプログラミングの目標は次のとおりです。Web上に用意されているAPIを活用することによって、他者が提供するサービスを自分のアプリケーションに組み込み、自分独自のサービスを構築できるようになることを目指します。

ここ数年で、複数のサービスを組み合わせて新しいサービスを作る事例は増えており、Mashup Awardsのようなコンテストも開催されています。

使い方と大まかな仕組み

入力フォームにTwitterのユーザ名を入力して、見つけるボタンを押すと、その人がフォローしているユーザの中から「顔っぽいアイコン」だけを見つけます。アイコンの中から、メガネや笑顔といった条件を指定してアイコンを抽出できます。判定の精度は自分で試してみてください。

TwitterのREST APIを使うと、Twitterに関する様々な情報が取得できます。今回はこれを使って、指定されたユーザのフォローしているユーザ一覧を取得しています。

取得したユーザ情報から、そのユーザのアイコンのURLを取得し、それをface.comの顔解析APIを用いて解析しています。face.comのAPIの利用には登録が必要(無料)です。ここから登録ができます。無料アカウントでは、1時間で最大5,000枚の画像の解析ができます。ここにツールがあるので、どのような解析結果がJSONで送信されてくるかを確かめることができます。Facebookとの連携などもできますが、少し難しいので、今回はやめておきます。

結果として表示されているアイコンにマウスカーソルを合わせると、ユーザ情報が取得できます。これはTwitter @AnywhereというTwitterのライブラリを利用しています。このライブラリを利用するにはアプリケーション登録をしてAPI Keyを得る必要があります。登録はここから(Twitterのユーザ登録が必要です)、関連するドキュメントはここにあります。

では、最終課題がんばりましょう (*゚▽゚)ノ !

面白そうなWeb API

Webには色々なWeb APIが公開されているので、うまく活用すれば面白いものができるかもしれません。
ここここにリンク集があります。

ソースコードなど

JavaScript(faceiconfinder.js)

ソースはここからダウンロードできます。 授業で紹介したjQueryを使っています。

/******************************************************
 * Face(*゚▽゚)ノIconFinder
 * Written By Manabu Sugiura(manabu at sfc.keio.ac.jp)
 * v1.0.0 - 2011.9.9
 ******************************************************/

/* グローバル変数 */
var profileIconsCache;//アイコン情報のキャッシュ(表示切り替えに使う)
var complete = 0;//解析済みのアイコンの数

/**
 * ページが読みこまれた時の処理
 */
$(function() {

    //ページの部品を初期化する
    initialize();
    
    //見つけるボタンがクリックされた時の処理
    $('#btn_submit').click(function() {
        profileIconsCache = new Array;
        complete = 0;
        enterLoadingMode();
        searchFaceIcon($('#input_name').val());
        return false;
    });
    
    //表示絞り込み用のラジオボタンが選択された時の処理
    $('#selector input:radio').click(function() {
        var key = $(this).val();
        showAllProfileIcons(key, 'true');
    });
});

/**
 * ページの見た目を初期化する
 */
function initialize() {
    $('#btn_submit').removeAttr('disabled');
    $('#loading').hide();
    $('#selector').hide();
    $('#radio_rall').attr({
        checked: 'checked'
    });
}

/**
 * ページの見た目を解析中の表示に変更する
 */
function enterLoadingMode() {
    $('#btn_submit').attr('disabled', 'disabed');
    showProgress('解析を開始します。');
    $('#loading').show();
    $('#result').empty();
    $('#selector').hide();
    $('#radio_rall').attr({
        checked: 'checked'
    });
}

/**
 * ページの見た目を解析終了時の表示に変更する
 */
function finalize(message) {
    $('#btn_submit').removeAttr('disabled');
    $('#loading').fadeOut();
    showProgress(message);
    $('#selector').fadeIn();
}

/**
 * Twitterのユーザ名(name)を指定して、
 * フォローしているユーザ(最大100件)のアイコンを検索する
 */
function searchFaceIcon(name) {
    $.getJSON('http://api.twitter.com/1/statuses/friends.json?callback=?', {
        cursor: -1,//最大で100件まで
        include_entities: false,
        screen_name: name
    }, function(data) {
        var following = data.users.length;//フォローしている人の数
        if (following > 0) {
            showProgress(following + 'ユーザのアイコンを取得しました。');
            for (var i = 0; i < data.users.length; i++) {
                detectFace(data.users[i], following);
            }
        } else {
            finalize('フォローしているユーザがいません。');
        }
    }).error(function() {//エラーが発生した場合
        finalize('フォローしているユーザの取得に失敗しました。');
    });
}

/**
 * 指定されたユーザ(user)が顔っぽいアイコンかどうかを判定する
 * followingはフォローしている人の合計(進捗状況の表示用)
 */
function detectFace(user, following) {
    var originalIcon = 'http://api.twitter.com/1/users/profile_image?screen_name=' +
    user.screen_name +
    '&size=original';
    //user.profile_image_urlを解析しても良いが画像が小さくて認識精度が落ちるので、
    //アップロードされているオリジナルの画像のURLをAPI経由で取得する
    
    var facecomProxy = 'http://web.sfc.keio.ac.jp/~manabu/pro-2011-9/faceiconfinder/proxy.php?callback=?';
    //ここでは独自のpoxyを使っているが、
    //getJSONを使えば、クロスドメインアクセスの制限を受けずにface.comのAPIに直接アクセスできる
    
    $.getJSON(facecomProxy, {
        urls: originalIcon
    }, function(data) {
        if (includeFaceData(data)) {
            var profileIcon = createProfileIconObject(user, data);
            profileIconsCache.push(profileIcon);
            showProfileIcon(profileIcon);
        }
    }).complete(function() {
        complete++;//通信が完了した数をカウントしておく
        showProgress('解析中です。 ' + complete + '/' + following);
        
        //全ユーザの解析が終わったかを判定する
        if (complete == following) {//複数の通信が同時に行われるので、通信が完了した総数を調べる
            finalize(complete + 'ユーザ中の' + profileIconsCache.length + 'ユーザが顔っぽいアイコンでした。');
        }
    });
}

/**
 * face.comの解析結果(data)から顔が解析されているか調べる
 */
function includeFaceData(data) {
    if (data.status == 'success' && defined(data.photos[0].tags)) {
        return data.photos[0].tags.length != 0;
    } else {
        return false;
    }
}

/**
 * Twitterのユーザ情報(user)とface.comの解析結果(data)をまとめたオブジェクトを作る
 */
function createProfileIconObject(user, data) {
    var profileIcon = new Object;
    
    //Twitterのユーザ情報をコピー
    profileIcon.screenName = user.screen_name;
    profileIcon.url = user.profile_image_url;
    
    //face.comの解析結果をコピー
    copyAttribute('face', data, profileIcon);
    copyAttribute('glasses', data, profileIcon);
    copyAttribute('smiling', data, profileIcon);
    
    return profileIcon;
}

/**
 * 指定された属性(key)を顔の解析結果(data)から、
 * アイコン情報を保持するオブジェクト(profileIcon)にコピーする
 * 解析結果がない場合は、その属性の値をfalseに設定しておく
 */
function copyAttribute(key, data, profileIcon) {
    if (defined(data.photos[0].tags[0].attributes[key])) {
        profileIcon[key] = data.photos[0].tags[0].attributes[key];
    } else {
        profileIcon[key] = {
            'value': false
        };
    }
}

/**
 * 処理の進捗状況のメッセージを表示
 */
function showProgress(message) {
    $('#progress').show();
    $('#progress').html(message);
}

/**
 * 指定されたkeyがvalueと一致するアイコンを全て表示する
 * すべて表示する場合はkey='face'、value=true
 */
function showAllProfileIcons(key, value) {
    //現在表示されているものをフィードアウトして、新しいHTMLを組み立てる
    $('#result').fadeOut(function() {
        $('#result').empty();
        for (var i = 0; i < profileIconsCache.length; i++) {
            var profileIcon = profileIconsCache[i];
            if (profileIcon[key].value == value) {
                showProfileIcon(profileIcon);
            }
        }
        //HTMLを書き終わったら、フェードイン
        $('#result').fadeIn();
    });
}

/**
 * 指定されたアイコンを表示する
 */
function showProfileIcon(profileIcon) {
    //アイコン用のimgタグを生成する
    $('#result').append($('<img />').attr({
        'class': 'icon',
        'src': profileIcon.url,
        'alt': profileIcon.screenName,
        'width': '48',
        'height': '48'
    }));
    
    //アイコンの間に1文字分の空白を入れる
    $('#result').append(' ');
}

/**
 * 属性が宣言されているか調べる
 */
function defined(key) {
    return (typeof key != 'undefined' && key != undefined);
}

/**
 * Ajax通信のタイムアウト設定(全通信で共通に設定しておく)
 */
$.ajaxSetup({
    timeout: 60000
});

/**
 * Twitter AnywhereのHovercardsを表示するための設定
 * imgタグのうち、classがiconの場合にHovercardを表示する
 * 表示するユーザ名をalt属性で指定する
 */
twttr.anywhere(function(twitter) {
    twitter('img.icon').hovercards({
        expanded: true,//初期状態を大きなカードに設定
        username: function(node) {
            //alt属性にユーザ名を指定するとホバーカードが表示されるようにしておく
            return node.alt;
        }
    });
});
      

HTML(index.html)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>顔(*゚▽゚)ノアイコン見つけったー</title>
    <link type="text/css" href="style.css" rel="stylesheet">
    <!--[if lt IE 9]>
      <script type="text/javascript" src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <script type="text/javascript" src="jquery.js">
    </script>
    <script type="text/javascript" src="http://platform.twitter.com/anywhere.js?id=HiqvdMZwsh18zjTT6ZFk8w&amp;v=1">
    </script>
    <script type="text/javascript" src="faceiconfinder.js">
    </script>
  </head>
  <body>
    <div class="main">
      <h1>顔 (*゚▽゚)ノ アイコン見つけったー</h1>
      <p>
        Twitterのユーザ名を入力して、見つけるボタンを押してください。<br/>
        その人がフォローしているユーザの中から「顔っぽいアイコン」だけを見つけます。<br/>
        解説は<a href="explanation/index.html">こちら</a></p>
      <a href="http://developers.face.com"><img src="img/badge.png" alt="face.com"/></a>
      <form>
        <label>
          <input id="input_name" class="input_name" value="sfckamoike">
        </label>
        <button id="btn_submit" class="btn_submit" type="submit">見つける</button>
      </form>
      <br/>
	  
      <!-- 解析中に表示するLoadingスピナー -->
      <div id="loading">
        <img src="img/loading.gif" alt="loading"/>
      </div>
	  
	  <!-- 進捗表示 -->
      <div id="progress">
      </div>
 
      <!-- 絞り込み表示用のラジオボタン(検索後に表示される) -->
      <div id="selector">
        <label>
          <input id="radio_rall" name="selector" type="radio" value="face"/>全て表示
        </label>
        <label>
          <input id="radio_rglasses" name="selector" type="radio" value="glasses"/>メガネっぽい人だけ表示
        </label>
        <label>
          <input id="radio_rsmiling" name="selector" type="radio" value="smiling"/>笑顔っぽい人だけ表示
        </label>
      </div>
	  
	  <!-- アイコン表示部分 -->
      <div id="result">
      </div>
    </div>
  </body>
</html>
戻る