Table of Contents
概要
- Twitter API を利用してタイムラインを表示します。
- 「Profile/StatusId」の横のテキストボックスに「https://twitter.com/#!/twj/status/90937652819398656」のようなリンクをペーストして同ボタンをクリックすると、そのツイート以前のツイートを表示します。
- 約200ツイート単位または一週間、二週間、一ヶ月単位で切替えられます。
- 前後はいずれも概算です。
- 古いツイートは表示されないようです。(max_idの制限?)
- Opera, Firefox, Chrome で動作確認。
- IE は table タグの innerHTML を書き換えられないようなので動きません。(仕様です)
- リンクは自前で判別せずにTweet Entitiesを使うようにしてるけど、結構リンクにならないことが多いな。
- 鍵付きのアカウントのツイートは読み取れません。
- URLの表示を短縮しないオプションを追加。(2011/12/10)
動作デモ
ソース
TTLBrowser.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Script-Type" content="text/javascript">
<meta http-equiv="Content-Style-Type" content="text/css">
<link rel="stylesheet" type="text/css" href="http://www.takeash.net/take.css">
<title>Twitter TimeLine Browser</title>
</head>
<body>
<h1>Twitter TimeLine Browser</h1>
<form name="form1" action="#">
<div id="Control">
<table>
<tr>
<th><button type="button" onclick="javascript:form1.tbSpecifiedId.value.match(/([^\/]+)(?:\/\w+\/(\d+))?$/); form1.tbUserName.value=RegExp.$1; browseTL(form1.tbUserName.value, RegExp.$2, form1.cbTruncate.checked);">Profile/StatusId</button></th>
<td><input type="text" id="tbSpecifiedId" name="tbSpecifiedId" value="" size="70"></td>
</tr>
<tr>
<th>UserName</th>
<td><input id="tbUserName" name="tbUserName" type="text" value="" size="70"></td>
</tr>
<tr><td colspan="2" align="center">
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, '', form1.cbTruncate.checked);">現在</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnAfterId.value, form1.cbTruncate.checked);">後の200件</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnAgoId.value, form1.cbTruncate.checked);">前の200件</button>
</td></tr>
<tr><td colspan="2" align="center">
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnOneMonthAfterId.value, form1.cbTruncate.checked);">一ヶ月後</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnTwoWeeksAfterId.value, form1.cbTruncate.checked);">二週間後</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnOneWeekAfterId.value, form1.cbTruncate.checked);">一週間後</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnOneWeekAgoId.value, form1.cbTruncate.checked);">一週間前</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnTwoWeeksAgoId.value, form1.cbTruncate.checked);">二週間前</button>
<button type="button" onclick="javascript:browseTL(form1.tbUserName.value, form1.hdnOneMonthAgoId.value, form1.cbTruncate.checked);">一ヶ月前</button>
</td></tr>
<tr><td colspan="2">
<input type="checkbox" id="cbTruncate" name="cbTruncate" checked> <label for="cbTruncate">URLの表示を短縮</label>
</td></tr>
</table>
<input type="hidden" id="hdnAfterId" name="hdnAfterId" value="" size="30">
<input type="hidden" id="hdnAgoId" name="hdnAgoId" value="" size="30">
<input type="hidden" id="hdnOneMonthAfterId" name="hdnOneMonthAfterId" value="" size="30">
<input type="hidden" id="hdnTwoWeeksAfterId" name="hdnTwoWeeksAfterId" value="" size="30">
<input type="hidden" id="hdnOneWeekAfterId" name="hdnOneWeekAfterId" value="" size="30">
<input type="hidden" id="hdnOneWeekAgoId" name="hdnOneWeekAgoId" value="" size="30">
<input type="hidden" id="hdnTwoWeeksAgoId" name="hdnTwoWeeskAgoId" value="" size="30">
<input type="hidden" id="hdnOneMonthAgoId" name="hdnOneMonthAgoId" value="" size="30">
</div>
<div id="Result">
<p id="ResultUserName"></p>
<table id="ResultTable">
</table>
</div>
</form>
<div id="Caution">
<h2>注意</h2>
<ul>
<li>Twitter API を利用してタイムラインを表示します。</li>
<li>「Profile/StatusId」の横のテキストボックスに「<a href="https://twitter.com/#!/twj/status/90937652819398656">https://twitter.com/#!/twj/status/90937652819398656</a>」のようなリンクをペーストして同ボタンをクリックすると、そのツイート以前のツイートを表示します。</li>
<li>約200ツイート単位または一週間、二週間、一ヶ月単位で切替えられます。</li>
<li>前後はいずれも概算です。</li>
<li>古いツイートは表示されないようです。(max_idの制限?)</li>
<li>Opera, Firefox, Chrome で動作確認。</li>
<li>IE は table タグの innerHTML を書き換えられないようなので動きません。(<a href="http://support.microsoft.com/kb/239832/ja">仕様です</a>)</li>
<li>リンクは自前で判別せずに<a href="https://dev.twitter.com/docs/tweet-entities">Tweet Entities</a>を使うようにしてるけど、結構リンクにならないことが多いな。</li>
</ul>
</div>
<div id="Link">
<h2>Link</h2>
<ul>
<li><a href="http://www.takeash.net/wiki/?JavaScript/Twitter_TimeLineBrowser">Twitter TimeLine Browser</a></li>
<li><a href="https://github.com/remy/twitterlib">remy Twitter Lib</a></li>
<li><a href="http://code.google.com/p/twitterjs/">remy twitterjs</a></li>
<li><a href="http://watcher.moe-nifty.com/memo/2007/04/twitter_api.html">Twitter API 仕様書 (勝手に日本語訳シリーズ)</a></li>
<li><a href="https://dev.twitter.com/docs/api">REST API Resources</a></li>
<li><a href="https://dev.twitter.com/docs/api/1/get/statuses/user_timeline">GET statuses/user_timeline</a></li>
<li><a href="https://dev.twitter.com/docs/tweet-entities">Tweet Entities</a></li>
</ul>
</div>
<script type="text/javascript" src="twitterlib_tak.js"></script>
<script type="text/javascript" src="TTLBrowser.js"></script>
</body>
</html>
TTLBrowser.js
function browseTL(username, maxid, truncate){
var twhost = 'https://twitter.com/#!/';
var options = {};
options.enableLinks = true;
options.truncateLinks = truncate;
// options.limit = 50;
options.target_username = document.getElementById('ResultUserName');
options.target_table = document.getElementById('ResultTable');
options.template_username = '<a href="' + twhost + '%user_screen_name%" target="_blank">'
+ '<img width="48" height="48" src="%user_profile_image_url%"></a> '
+ '<a href="'+twhost+'%user_screen_name%" target="_blank">%user_name% '
+ '(%user_screen_name%)</a>';
options.template_head = '<tr><th>#</th><th>日時</th><th>本文</th><th>返信</th></tr>';
options.template_table = '<tr><td align="center"><button type="button" '
+ 'onclick="javascript:browseTL(form1.tbUserName.value, \'%id_str%\');">%index%</button></td>'
+ '<td><a href="' + twhost + '%user_screen_name%/status/%id_str%" target="_blank">%time%</a></td>'
+ '<td>%text%</td><td align="center">%reply%</td>'
+ '</tr>';
options.maxid = (maxid && maxid.match(/^\d+$/)) ? maxid : undefined ;
options.tags = {};
options.tags.afterid = document.getElementById('hdnAfterId');
options.tags.agoid = document.getElementById('hdnAgoId');
options.tags.onemonthafterid = document.getElementById('hdnOneMonthAfterId');
options.tags.twoweeksafterid = document.getElementById('hdnTwoWeeksAfterId');
options.tags.oneweekafterid = document.getElementById('hdnOneWeekAfterId');
options.tags.oneweekagoid = document.getElementById('hdnOneWeekAgoId');
options.tags.twoweeksagoid = document.getElementById('hdnTwoWeeksAgoId');
options.tags.onemonthagoid = document.getElementById('hdnOneMonthAgoId');
twitterlib_tak.timeline( username, options, function(tweets, options){
var html = [];
html.push( options.template_head );
for (var i = 0; i < tweets.length; ++i){
tweets[i].time = twitterlib_tak.time.localtime( tweets[i].created_at );
tweets[i].reply = tweets[i].in_reply_to_status_id_str
? '<a href="' + twhost + tweets[i].in_reply_to_screen_name + '/status/'
+ tweets[i].in_reply_to_status_id_str + '" target="_blank">元</a> '
+ '<button type="button" onclick="javascript:form1.tbUserName.value=\''
+ tweets[i].in_reply_to_screen_name + '\'; browseTL(\''
+ tweets[i].in_reply_to_screen_name + '\', \''
+ tweets[i].in_reply_to_status_id_str + '\');">表示</button>'
: '';
tweets[i].index = i + 1;
for (var key in tweets[i].user) {
tweets[i]['user_' + key] = tweets[i].user[key];
}
if (options.template_table) {
html.push( options.template_table.replace(/%([a-z_\-\.]*)%/ig, function (m, l){
var r = tweets[i][l] + "" || "";
if (l == 'text') {
r = twitterlib_tak.expandLinks(tweets[i], options.truncateLinks);
r = r.replace( /\n/g, "<br>\n" );
}
return r;
}));
} else {
html.push(twitterlib_tak.render(tweets[i]));
}
}
options.target_table.innerHTML = html.join('');
if ( tweets.length > 0 ){
options.target_username.innerHTML =
options.template_username.replace(/%([a-z_\-\.]*)%/ig, function (m, l){
var r = tweets[0][l] + "" || "";
if (l == 'text') r = twitterlib_tak.expandLinks(tweets[0], options.truncateLinks);
return r;
});
if ( tweets.length > 1 ){
var newest_id = parseInt( tweets[0].id_str );
var oldest_id = parseInt( tweets[tweets.length - 1].id_str );
var newest_sec = new Date( tweets[0].created_at ).getTime();
var oldest_sec = new Date( tweets[tweets.length - 1].created_at ).getTime();
var diff_id = newest_id - oldest_id;
var postrate = diff_id / (( newest_sec - oldest_sec) / (24 * 3600 * 1000));
options.tags.afterid.value = newest_id + diff_id;
options.tags.agoid.value = oldest_id;
options.tags.onemonthafterid.value = newest_id + postrate * 30;
options.tags.twoweeksafterid.value = newest_id + postrate * 14;
options.tags.oneweekafterid.value = newest_id + postrate * 7;
options.tags.oneweekagoid.value = newest_id - postrate * 7;
options.tags.twoweeksagoid.value = newest_id - postrate * 14;
options.tags.onemonthagoid.value = newest_id - postrate * 30;
}
}
});
}
twitterlib_tak.js
- remy Twitter Libとの差分
--- RemySharp\twitterlib.js 2011-10-31 10:31:23.444145000 +0900
+++ twitterlib_tak.js 2011-12-08 21:22:34.251240700 +0900
@@ -1,5 +1,7 @@
// twitterlib.js (c) 2011 Remy Sharp
// Licensed under the terms of the MIT license.
+// Original: https://github.com/remy/twitterlib
+// Modified by TakeAsh
(function (twitterlib, container) {
var guid = +new Date,
window = this,
@@ -13,11 +15,11 @@
'>': '>'
},
URLS = {
- search: 'http://search.twitter.com/search.json?q=%search%&page=%page|1%&rpp=%limit|100%&since_id=%since|remove%&result_type=recent', // TODO allow user to change result_type
- timeline: 'http://api.twitter.com/1/statuses/user_timeline.json?screen_name=%user%&count=%limit|200%&page=%page|1%&since_id=%since|remove%include_rts=%rts|false%&include_entities=true',
- list: 'http://api.twitter.com/1/%user%/lists/%list%/statuses.json?page=%page|1%&per_page=%limit|200%&since_id=%since|remove%&include_entities=true&include_rts=%rts|false%',
- favs: 'http://api.twitter.com/1/favorites/%user%.json?page=%page|1%include_entities=true&skip_status=true',
- retweets: 'http://api.twitter.com/1/statuses/retweeted_by_user.json?screen_name=%user%&count=%limit|200%&since_id=%since|remove%&page=%page|1%'
+ search: 'https://search.twitter.com/search.json?q=%search%&page=%page|1%&rpp=%limit|100%&since_id=%since|remove%&result_type=recent', // TODO allow user to change result_type
+ timeline: 'https://api.twitter.com/1/statuses/user_timeline.json?id=%user%&count=%limit|200%&page=%page|1%&since_id=%since|remove%include_rts=%rts|false%&max_id=%maxid|remove%&include_entities=true',
+ list: 'https://api.twitter.com/1/%user%/lists/%list%/statuses.json?page=%page|1%&per_page=%limit|200%&since_id=%since|remove%&include_entities=true&include_rts=%rts|false%',
+ favs: 'https://api.twitter.com/1/favorites/%user%.json?page=%page|1%include_entities=true&skip_status=true',
+ retweets: 'https://api.twitter.com/1/statuses/retweeted_by_user.json?id=%user%&count=%limit|200%&since_id=%since|remove%&page=%page|1%'
},
urls = URLS, // allows for resetting debugging
undefined,
@@ -51,7 +53,7 @@
};
}();
- var expandLinks = function (tweet) {
+ var expandLinks = function (tweet, truncateLinks) {
if (tweet === undefined) return '';
var text = tweet.text,
@@ -60,14 +62,48 @@
// replace urls with expanded urls and let the ify shorten the link
if (tweet.entities.urls && tweet.entities.urls.length) {
for (i = 0; i < tweet.entities.urls.length; i++) {
- if (tweet.entities.urls[i].expanded_url) text = text.replace(tweet.entities.urls[i].url, tweet.entities.urls[i].expanded_url); // /g ?
+ var link = tweet.entities.urls[i].expanded_url;
+ if ( link ) {
+ text = text.replace(tweet.entities.urls[i].url, '<a title="' + link + '" href="'+ link + '">'+((truncateLinks && link.length > 36) ? link.substr(0, 35) + '…' : link)+'</a>');
+ }
}
}
// replace media with url to actual image (or thing?)
if (tweet.entities.media && tweet.entities.media.length) {
for (i = 0; i < tweet.entities.media.length; i++) {
- if (tweet.entities.media[i].media_url || tweet.entities.media[i].expanded_url) text = text.replace(tweet.entities.media[i].url, tweet.entities.media[i].media_url ? tweet.entities.media[i].media_url : tweet.entities.media[i].expanded_url); // /g ?
+ var imgurl = tweet.entities.media[i].media_url_https || tweet.entities.media[i].media_url || tweet.entities.media[i].expanded_url;
+ if ( imgurl ) {
+ text = text.replace(tweet.entities.media[i].url, '<a href="' + imgurl + '" title="' + imgurl + '" target="_blank">' + ((truncateLinks && imgurl.length > 36) ? imgurl.substr(0, 35) + '…' : imgurl) + '</a>' ); // /g ?
+ text += '\n<a href="' + imgurl + '" target="_blank"><img src="' + imgurl + '" width="' + tweet.entities.media[i].sizes.small.w + '" height="' + tweet.entities.media[i].sizes.small.h + '"></a>'
+ }
+ }
+ }
+
+ // replace user mentions with user link
+ if ( tweet.entities.user_mentions && tweet.entities.user_mentions.length ) {
+ tweet.entities.user_mentions.sort( function( a, b ){ return ( a.screen_name.length < b.screen_name.length ) ? 1 : -1 ; } );
+ for (i = 0; i < tweet.entities.user_mentions.length; i++) {
+ var screen_name = tweet.entities.user_mentions[i].screen_name;
+ var re = new RegExp( '@'+screen_name, 'gi' );
+ if ( screen_name ) text = text.replace( re, '@<a href="https://twitter.com/' + screen_name + '">' + screen_name + '</a>');
+ }
+ }
+
+ // replace hash tag with search link
+ if ( tweet.entities.hashtags && tweet.entities.hashtags.length ) {
+ //alert( tweet.entities.hashtags.length );
+ tweet.entities.hashtags.sort( function( a, b ){ return ( a.text.length < b.text.length ) ? 1 : -1 ; } );
+ for (i = 0; i < tweet.entities.hashtags.length; i++) {
+ var hashtext = tweet.entities.hashtags[i].text;
+ var re = new RegExp( "(#|#)" + hashtext, 'g' );
+ if ( hashtext ) {
+ //alert(hashtext);
+ if ( text.match( re ) ){
+ //alert(RegExp.$1 + "\n" + hashtext + "\n" + text);
+ }
+ text = text.replace( re, '$1<a href="https://search.twitter.com/search?q=%23' + encodeURIComponent(hashtext) + '">' + hashtext + '</a>');
+ }
}
}
@@ -170,7 +206,10 @@
}
return r;
- }
+ },
+ localtime: function(time_value){
+ return new Date( time_value ).toLocaleString();
+ }
};
}();
@@ -318,7 +357,7 @@
html += tweet.user.screen_name + '" ';
html += 'title="' + tweet.user.name + '">' + tweet.user.screen_name + '</a></strong> ';
html += '<span class="entry-content">';
- html += container[twitterlib].ify.clean(container[twitterlib].expandLinks(tweet));
+ html += container[twitterlib].expandLinks(tweet);
html += '</span> <span class="meta entry-meta"><a href="http://twitter.com/' + tweet.user.screen_name;
html += '/status/' + tweet.id_str + '" class="entry-date" rel="bookmark"><span class="published" title="';
html += container[twitterlib].time.datetime(tweet.created_at) + '">' + container[twitterlib].time.relative(tweet.created_at) + '</span></a>';
@@ -590,4 +629,4 @@
container[twitterlib].custom('timeline');
container[twitterlib].custom('favs');
container[twitterlib].custom('retweets');
-})('twitterlib', this);
+})('twitterlib_tak', this);