Twitter TimeLine Browser

概要

  • 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)

動作デモ

ソース

  • &ref(): File not found: "TTLBrowser.zip" at page "JavaScript/Twitter_TimeLineBrowser";

TTLBrowser.html

  0
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
<!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

すべてを展開すべてを収束
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
-
|
|
|
|
|
|
|
|
|
|
-
|
!
-
-
|
-
|
|
!
|
!
-
|
!
!
|
-
|
-
|
|
|
!
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
!
!
!
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

--- 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 @@
         '&gt;': '>'
       },
       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) + '&hellip;' : 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) + '&hellip;' : 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);

リロード   新規 下位ページ作成 編集 凍結 差分 添付 コピー 名前変更   ホーム 一覧 検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS
Last-modified: Thu, 25 May 2017 04:51:31 JST (200d)