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