解説
これは何?
これは論理思考とプログラミング~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&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>