Twittelien ver0.06

2008/04/11追記

発言の際にPOSTするURL、パラメタがまた変更になったので対応しました。

2008/04/09追記

/homeのHTMLが変更になったのでとりあえず対応しました。
発言時にPOSTすべきURLがhttp://twitter.com/status/updateではなくなったので対応しました。

使い方

  1. http://twitter.com/homeをブラウザで開く
  2. このbookmarkletを使うなどしてjQueryをロード
  3. FirebugJashなどのJavaScriptコンソールから以下のソースを順繰りに流していく


この使い方だといい加減使い勝手悪すぎなので、
早くどこかのサーバにファイルを置いてbookmarklet化しなければならない。

ソース(本体+プラグイン用関数)

// 検索ボックスを半透明化して左に、
(function($){
	$('#navigation').remove();
	var sideOffset = $('#side').offset();
	$('#side')
		.css({	'position':'fixed'
				,'left':sideOffset.left
				,'top':20
			});
	$('#doingForm')
		.css({	'left':20
				,'top':130
				,'position':'fixed'
				,'z-index':'1'
				,'opacity':'0.1'
			})
		.find(':button')
			.insertBefore('#doingForm center');
	$('#status')
		.bind('focus',function(){
					$('#doingForm').css({'opacity':'0.8'});
				})
		.bind('blur',function(){
					$('#doingForm').css({'opacity':'0.1'});
				});
})(jQuery);
//自動更新中に発言できるように
(function($){
	var authenticity_token = $('#doingForm [name=authenticity_token]').val();
	var siv = $('#doingForm [name=siv]').val();
	$('#doingForm .update-button')
		.removeAttr('onclick')
		.bind('click',function(){
			var status = $('#status').val();
			$('#status').val('');
			$.ajax({
				type:'POST',
				url:'http://twitter.com/status/update?page=1&tab=home',
				data:{status:status,authenticity_token:authenticity_token,siv:siv}
			});
	});
})(jQuery);
//本体
Twittelien = {};
Twittelien.entryFilters = {};
Twittelien.reply = {};
Twittelien.etc = {};
(function($){
	function Appender(params){
		var self = this;
		if(typeof params.scrape != 'function'){
			throw new Error('scrape isn\'t function;');
		}
		if(typeof params.append != 'function'){
			throw new Error('append isn\'t function;');
		}
		var scrape = params.scrape;
		var append = params.append;
		var isStopped = false;
		
		this.stop = function(){isStopped = true;};
		this.start = function(){
			var func = arguments.callee;
			var started = new Date();
			$.ajax({
				type:'GET',
				url:self.url,
				success:function(data){
					var scrapeStarted = new Date();
					var entries = scrape(data);
					var filterStarted = new Date();
					entries = filter(entries);
					var beforeAppendStarted = new Date();
					beforeAppend(entries);
					var appendStarted = new Date();
					append(entries);
					var afterAppendStarted = new Date();
					afterAppend(entries);
					self.log([	['req&res',scrapeStarted - started].join(':')
								,['scrape',filterStarted - scrapeStarted].join(':')
								,['filter',beforeAppendStarted - filterStarted].join(':')
								,['beforeAppend',appendStarted - beforeAppendStarted].join(':')
								,['append',afterAppendStarted - appendStarted].join(':')
								,['afterAppend',new Date() - afterAppendStarted].join(':')
							].join());
				},
				error:function(xhr,textStatus){
					self.log([	'error',['req&res',new Date() - started].join(':')	].join());
				},
				complete:function(xhr,textStatus){
					if(isStopped){
						return;
					}
					setTimeout(func,self.interval);
				}
			});
		};
		
		var filterFunction = params.filter || function(){};
		var beforeAppendFunctions = [];
		var afterAppendFunctions = [];
		
		this.setFilter = function(func){
			if(typeof func == 'function'){
				filterFunction = func;
			}
		};
		this.addBeforeAppend = addFunction(beforeAppendFunctions);
		this.addAfterAppend = addFunction(afterAppendFunctions);
		function addFunction(functions){
			return function(func){
					if(typeof func == 'function'){
						functions.push(func);
					}
				}
		}
		function filter(entries){
			if(typeof entries != 'object' || !(entries instanceof jQuery) ){
				return;
			}
			return entries.filter(filterFunction);
		}
		function beforeAppend(entries){
			if(typeof entries != 'object' || !(entries instanceof jQuery) ){
				return;
			}
			for(var i = 0; i < beforeAppendFunctions.length; i++){
				entries
					.each(function(){beforeAppendFunctions[i].apply(this);});
			}
		}
		function afterAppend(entries){
			if(typeof entries != 'object' || !(entries instanceof jQuery) ){
				return;
			}
			for(var i = 0; i < afterAppendFunctions.length; i++){
				afterAppendFunctions[i].apply(entries);
			}
		}
	};
	Appender.prototype.log = function(){};
	Appender.prototype.url = 'http://twitter.com/home';
	Appender.prototype.interval = 30*1000;
	
	Twittelien.Appender = Appender;
})(jQuery);
// 重複判定
(function($){
	Twittelien.entryFilters.uniq =  function(index){
		return $( '#' + $(this).attr('id') ).size() < 1;
	}
})(jQuery);
//replyアイコンclickイベント周り
(function($){
	// 元々設定されているonclickイベントを剥がす
	Twittelien.reply.removeDefaultClickEvent = function(){
		$('a:has(img[title^=reply to])',this)
			.each(function(){
					$(this)
						.attr('onclick','')
						.attr('href','javascript:void(0);');
				});

	}
})(jQuery);
(function($){
	// @usernameをステータス入力欄の先頭に付加する。
	Twittelien.reply.copyUserName = function(){
		$(this)
			.each(function(){
					var userName = $('.content>strong>a',this).text();
					$('a:has(img[title^=reply to])',this)
						.bind('click',function(){
									$('#status').val(['@',userName,' ',$('#status').val()].join(''));
								});
				});
	}
})(jQuery);
(function($){
	// 選択範囲を引用する
	Twittelien.reply.quoteSelectedText = function(){
		$('a:has(img[title^=reply to])',this)
			.bind('click',function(){
						var quotedText;
						//Firefox・Opera
						if(typeof window.getSelection == 'function'){
							quotedText = window.getSelection().toString();
						}
						if(!quotedText || quotedText.length < 1){
							return;
						}
						$('#status').val([$('#status').val(),' ', quotedText].join(''));
					});
	}
})(jQuery);
(function($){
	// ステータス入力欄にフォーカスする
	Twittelien.reply.focusToStatus = function(){
		$('a:has(img[title^=reply to])',this)
			.bind('click',function(){
						$('#status').focus();
					});
	}
})(jQuery);
// ゴミ箱アイコンクリックで自エントリ削除可能にする
(function($){
	Twittelien.etc.del = function(){
		$(this)
			.each(function(){
				var a = $('a[title=Delete this update?]',this)
				var entry = $(this);
				a.attr('onclick','')
					.bind('click',function(){
						$.ajax({
							type:'POST',
							url:['http://twitter.com/statuses/destroy/',
									a.attr('href').replace('/status/destroy/','')
									,'.json'].join(''),
							dataType:'json',
							success:function(json){
								entry.remove();
							}
						});
						return false;
					});
			});
	}
})(jQuery);
// 発言をクリックするとユーザのページに遷移してしまうのを防止
(function($){
	Twittelien.etc.removeOnclick = function(){
		$(this).removeAttr('onclick');
	}
})(jQuery);

インスタンス生成、関数設定、スタート

(function($){
	var timeline = $('#container #timeline');
	function scrape(html){
		var temp = $('<div/>');
		temp.get(0).innerHTML = html;
		return temp.find('#timeline tr.hentry_hover');
	}
	function append(entries){
		timeline.find('tr.hentry_hover:first')
			.before(entries);
	}
	recent = new Twittelien.Appender({'scrape':scrape,'append':append,'filter':Twittelien.entryFilters.uniq});
})(jQuery);
// 関数設定((不要なもの外してOK)
(function($){
	recent.addBeforeAppend(Twittelien.etc.removeOnclick);
	with(Twittelien.reply){
		recent.addBeforeAppend(removeDefaultClickEvent);
		recent.addBeforeAppend(copyUserName);
		recent.addBeforeAppend(quoteSelectedText);
		recent.addBeforeAppend(focusToStatus);
	}
	recent.addBeforeAppend(Twittelien.etc.del);
})(jQuery);
// 自動更新開始
recent.start();

なお、↓で自動更新を停止できます。

recent.stop();