/*
= 2007/09/23
	- Photo - Snipshot 画像のもとページ情報を使ってポスト

= 2007/09/12
	- Photo - Snipshot 仕様変更に対応
	- FFFFOUND! 重複エラーを非表示に
	- 画像保護サイト警告追加
	- Tumblr/PhotoをオリジナルからのReBlogに
	
= 2007/07/13
	- Photo - LDR Linkが追加されてしまう現象を修正
	
= 2007/07/13
	- blogspotで拡張子が大文字の画像での不具合を修正
	- Photo - Snipshot 仕様変更に対応
	- Quote - Twitter 追加(タイトルをコンパクトに)
	- LDRにQuote/Linkを追加(不要パラメーターの除去処理も追加、39 otsune)
	- Photo - FFFFOUND! 追加(ユーザーへのリンクなどを追加)
	- AREA要素の場合、ターゲットの画像をPhotoタイプでポスト (39 piro)

= 2007/06/10
	- Ctrlキー押下で繰り返しonloadイベントが発生する不具合を修正
	- Shiftキー押下でタイトル編集
	- FFFFOUND! エラー処理追加
	- FFFFOUND! I LOVE THIS 対応
	- Photo制限エラーをより正確に

= 2007/06/08
	- ReBlog時のPhoto制限チェックを追加
	- 画像表示ページからポストする際にPhoto制限が出てしまう不具合を修正
	- Photo/ReBlog - LDRを追加 (39 youpy)
	- FFFFOUND!同時ポスト追加

= 2007/06/06
	- Amazon 対応
	- Photo - qoob.tv (via youpy) 追加
	- Photo - Snipshot 追加(編集画面からダイレクトポスト)
	- Photo - blogspot 追加
	- Video - Stage6 追加
	- Ctrlキー押下で編集画面へ遷移
	- Photoの容量制限チェック
	- Photo - Flickr(large)タイトル取得バグ修正
	- ダイレクトReBlog
	- バックグラウンドポスト対応
	- Videoオブジェクトの幅を調整

= 2007/05/20
	- Video - vimeo 追加
	- Video - dailymotion 追加
	- Dashboard上でのReBlog不具合を修正
	- Video - Rimoのタイトルをコンパクトに

= 2007/05/19
	- 独自ドメインのTumblrからのReBlog
	- ReBlog後の遷移先をDashboardに変更
	- Dashboard上のエントリでReBlog(自分のエントリも可)
	- Video - Rimo 追加(番組情報ページから)

= 2007/05/17
	- 画像を直接開いている場合のタイトルをファイル名のみに変更
	- Ctrlキー+メニュークリックでタイトル編集を可能に
	- ポスト後自動でウィンドウを閉じるように(注意! 容量超過エラー補足できず)
	- Linkタイプのポスト追加

= 2007/05/12
	- TumblrではReBlog
	- Flickrの拡大イメージに対応

= 2007/05/07
	- objectイメージ対応
	- YouTubeの引用にviaを付加

= 2007/05/04 18:20
*/


var AMAZON_AFFILEATE_ID = '';
var AMAZON_IMAGE_MIN_SIZE = 9999;
var POST_TO_FFFFOUND = false;

var LINK_ADD_THUMBNAIL = false;

var THEME_IMAGE_WIDTH = 500;

var SHIFT_KEY = 1;
var CTRL_KEY = 2;

const IMAGE_PROTECTED_SITES = [
	'^http://www.imgscan.com/image_c.php',
	'^http://keep4u.ru/imgs/'
];

// クローニングを行いgetterメソッドの値を確定し保存する
// addTabなどで別コンテキストへ移動したときに使用
var location = update({}, window.location);
var context = update(update({}, _jsaCScript.context), _jsaCScript);
context.title = ''+document.title;
context.menu = update({}, gContextMenu.menu.boxObject);

[
	// Photo - area element
	[
		function(){
			return tagName(context.target)=='area';
		},
		function(){
			var image = getElementByPosition(context.menu.x, context.menu.y);
			
			if(!image.src){
				alert('It failed to get the image under the area element.');
				return;
			}
			
			postPhoto({
				'photo_src' : image.src,
			});
		}
	],
	
	// Photo/Link/Quote - amazon.co.jp
	[
		function(){
			return location.hostname.match(/amazon\.co\.jp/);
		},
		function(){
			var asin = document.getElementById('ASIN').value;
			amazon(asin, function(item){
				var url = 'http://amazon.co.jp/o/ASIN/' + asin + 
					(AMAZON_AFFILEATE_ID ? '/' + AMAZON_AFFILEATE_ID + '/ref=nosim' : '');
				var title = item.title + (item.creators.length? ' / '+item.creators.join(', ') : '');
				var link = A(url, title);
				var target = context.target;
				
				// Photo - Amazon
				if(target.id == 'prodImage'){
					var img = item.largestImage;
					if(!img){
						alert('Image not found.');
						return;
					}
					
					with(target){
						src = img.URL;
						height = '';
						width = '';
						style.height = 'auto';
						style.width = 'auto';
					}
					
					setTimeout(function(){
						if(img.h<AMAZON_IMAGE_MIN_SIZE && img.w<AMAZON_IMAGE_MIN_SIZE){
							if(!confirm('Image size: '+img.w+'×'+img.h + '\nWill you post?'))
								return;
						}
						
						return postPhoto({
							'post[two]' : link,
							'photo_src' : img.URL,
						});
					}, 0)
				}
				
				// Quote - Amazon
				if(context.isTextSelected)
					return postTumblr({
						'post[type]' : 'quote',
						'post[two]' : link,
						'post[one]' : context.selection,
					});
				
				// Link - Amazon
				postTumblr({
					'post[type]' : 'link',
					'post[one]' : title,
					'post[two]' : url,
				});
			});
		}
	],
	
	// ReBlog() Photo- *
	[
		function(){
			return context.onImage && 
				context.target.src.match('http://data.tumblr.com/.*.jpg')
		},
		function(){
			reblog(context.target.src.match('http://data.tumblr.com/(.*?)_')[1]);
		}
	],
	
	// Photo/ReBlog/Quote/Link - LDR
	[
		function(){
			return location.hostname == 'reader.livedoor.com';
		},
		function(){
			var target = context.target;
			var item = $x('ancestor::div[starts-with(@id, "item_count")]', context.target)
			var title = $x('div[@class="item_header"]//a/text()', item)
			var feed = $x('id("right_body")/div[@class="channel"]//a/text()')
			var url = $x('(div[@class="item_info"]/a)[1]/@href', item)
			url = url.replace(/[¥?&;](fr?(om)?|track|ref|FM)=(r(ss(all)?|df)|atom)([&;].*)?/,'');
			
			// Quote
			if(context.isTextSelected){
				postTumblr({
					'post[type]' : 'quote',
					'post[one]'  : context.selection,
					'post[two]'  : A(url, feed + ' - ' + title),
				});
				return;
			}
			
			// ReBlog - Tumblr
			if(url.match('^http://.*?\\.tumblr\\.com/')){
				reblog(url.split('/').pop());
				return;
			}
			
			// Photo
			if(tagName(target)=='img'){
				postPhoto({
					'photo_src' : target.src,
					'post[two]' : A(url, feed + ' - ' + title),
				});
				return;
			}
			
			// Link
			postTumblr({
				'post[type]' : 'link',
				'post[one]' : feed + ' - ' + title,
				'post[two]' : url,
				'post[three]' : LINK_ADD_THUMBNAIL ? '<img src="http://mozshot.nemui.org/shot?'+url+'" />' : '',
			});
		}
	],
	
	// Quote - Twitter
	[
		function(){
			return location.hostname == 'twitter.com' && context.isTextSelected;
		},
		function(){
			postTumblr({
				'post[type]' : 'quote',
				'post[one]'  : context.selection,
				'post[two]'  : A(location.href, context.title.substring(0, context.title.indexOf(': '))),
			});
		}
	],
	
	// Quote
	[
		function(){
			return context.isTextSelected;
		},
		function(){
			postTumblr({
				'post[type]' : 'quote',
				'post[one]'  : context.selection,
			});
		}
	],
	
	// ReBlog - Tumblr
	[
		function(){
			return $x('//iframe[starts-with(@src, "http://www.tumblr.com/publisher/iframe")]');
		},
		function(){
			var link = $x(
				'(ancestor::div[@class="post"]//a[starts-with(@href,"http://'+location.host+'/post/")])[1]/@href', 
				context.target);
			if(!link) return;
			
			reblog(link.split('/').pop());
		}
	],
	
	// ReBlog - Dashbord
	[
		function(){
			return location.href.match('^http://www.tumblr.com/dashboard');
		},
		function(){
			var target = context.target;
			var parent = tagName(target)=='li' ? target : $x('ancestor::li', target);
			var control = $x('div[@class="control"]/div', parent);
			
			if(!control) return;
			
			reblog(control.id.split('_')[1]);
		}
	],
	
	// Photo - Snipshot
	[
		function(){
			return location.href.match('http://services.snipshot.com/edit/');
		},
		function(){
			var id = window.m ? window.m.id : window.snipshot.FILE;
			var info = window.SnipshotImport;
			if(info){
				location.href = info.url;
				context.title = info.title;
			}
			
			postPhoto({
				'post[two]' : info? A(info.url, info.title) : '',
				'photo_src' : 'http://services.snipshot.com/save/'+id+'/snipshot_'+id+'.jpg',
			});
		}
	],
	
	// Photo - flickr.com
	[
		function(){
			return context.onImage &&
				location.href.match('flickr.com/photos/');
		},
		function(){
			var info = $x('//div[@class="Widget"]/a[last()]');
			var title = context.title.match('(.*) ?on Flickr')[1] || 'no title';
			var img = context.target;
			
			if(img.src.match('spaceball.gif'))
				img = img.previousSibling;
			
			postPhoto({
				'post[two]' : A(location.href, title) + ' (via ' + A(info.href, info.textContent) + ')',
				'photo_src' : img.src,
			});
		}
	],
	
	// Photo - flickr.com(Large)
	[
		function(){
			return context.onImage &&
				location.href.match('flickr.com/photo_zoom.gne');
		},
		function(){
			var info = $x('//div[@class="Owner"]/a[last()]');
			var title = extract(context.title, 'Flickr Photo Download: (.*)') || 'no title';
			var img = context.target;
			
			if(img.src.match('spaceball.gif'))
				img = img.previousSibling;
			
			postPhoto({
				'post[two]' : A(location.href, title) + ' (via ' + A(info.href, info.textContent) + ')',
				'photo_src' : img.src,
			});
		}
	],
	
	// Photo - qoob.tv (via youpy)
	[
		function(){
			return location.href.match('en.qoob.tv/pict/clip_view.asp');
		},
		function(){
			var info = $x('//a[@class="link_people"][1]');
			var title = context.title.match('QOOB - PICT - (.*)')[1] || 'no title';
			var img_src = location.href.split('clip_view').join('download');
			
			postPhoto({
				'post[two]' : A(location.href, title) + ' (via ' + A(info.href, info.textContent) + ')',
				'photo_src' : img_src,
			});
		}
	],
	
	// Photo - blogger(blogspot)
	[
		function(){
			return context.onLink && 
				(''+context.link).match(/(png|gif|jpe?g)$/i) &&
				(''+context.link).match(/blogger.com\/.*\/s\d{2,}-h\//);
		},
		function(){
			postPhoto({
				'photo_src' : (''+context.link).replace(/\/(s\d{2,})-h\//, '/$1/'),
			});
		}
	],
	
	// Photo - FFFFOUND!
	[
		function(){
			return location.href.match('http://ffffound.com/image/') && 
				context.onImage && 
				context.target.src.match(/^[^?]*/)[0].match(/_m\.(png|gif|jpe?g)$/i);
		},
		function(){
			var info = $x('//div[@class="saved_by"]/a[1]');
			
			postPhoto({
				'post[two]' : A(location.href, context.title) + ' (via ' + A(info.href, info.textContent) + ')',
				'photo_src' : context.target.src.replace(/_m(\..{3})$/, '$1'),
			});
		}
	],
	
	// Photo - image link
	[
		function(){
			return (context.onLink && context.link.href.match(/^[^?]*/)[0].match(/(png|gif|jpe?g)$/i));
		},
		function(){
			postPhoto({
				'photo_src' : context.link.href,
			});
		}
	],
	
	// Photo - normal
	[
		function(){
			return context.onImage;
		},
		function(){
			var target = context.target;
			var params = {
				'photo_src' : tagName(target)=='object'? target.data : target.src,
			};
			if(document.contentType.match(/^image/)){
				var url = location.href;
				params['post[two]'] = A(url, url.split('/').pop());
			}
			
			postPhoto(params);
		}
	],
	
	// Video - Vimeo
	[
		function(){
			return location.hostname.match('vimeo.com');
		},
		function(){
			var tag = $x('id("share")/textarea');
			if(!tag) return;
			
			tag = tag.value;
			postVideo({
				'post[one]' : extract(tag, /^(.*)/),
				'post[two]' : extract(tag, /(<a.*?a>)/g, 0) + ' (via ' + extract(tag, /(<a.*?a>)/g, 1) + ')',
			});
		}
	],
	
	// Video - MySpace
	[
		function(){
			return location.hostname.match(/vids\.myspace\.com/);
		},
		function(){
			var tag = $x('id("flashcontent")//following-sibling::input[last()]/@value');
			if(!tag) return;
			
			postVideo({
				'post[one]' : extract(tag, /(<embed.*?embed>)/, 0),
				'post[two]' : extract(tag, /^(<a.*?a>)/, 0),
			});
		}
	],
	
	// Video - dailymotion
	[
		function(){
			return location.hostname.match('dailymotion.com');
		},
		function(){
			var tag = $x('//div[@class="video_player_embed"]/input');
			if(!tag) return;
			
			// タイトル文字化け回避のためcontext.titleから取得
			tag = tag.value;
			postVideo({
				'post[one]' : extract(tag, /(<object.*object>)/),
				'post[two]' : A(
					extract(tag, /href="(.*?)"/), 
					extract(context.title, /Video (.*?) -/)) + ' (via ' + 
					extract(tag, /(<a.*?a>)/g, 1) + ')',
			});
		}
	],
	
	// Video - Stage6
	[
		function(){
			return location.hostname.match(/stage6\.divx\.com/);
		},
		function(){
			var user = $x('//div[@class="video-cite"]//a[@class="entity"]');
			var title = $x('(//h1)[1]/text()');
			var link = $x('id("control-embed")/a[last()]/@href');
			var tag = $x('//input[@class="share-url"][last()]/@value');
			
			postVideo({
				'post[one]' : tag,
				'post[two]' : A(link, title) + ' (via ' + A(user.href, user.textContent) + ')',
			});
		}
	],
	
	// Video - Rimo
	[
		function(){
			return location.hostname == 'rimo.tv';
		},
		function(){
			var tag = $x('(//table[@class="player-embed-tags"]//input)[last()]');
			
			postVideo({
				'post[one]' : tag.value,
				'post[two]' : A(location.href, $x('id("play_list_title")/@value')),
			});
		}
	],
	
	// Video - YouTube
	[
		function(){
			return location.hostname.match('youtube.com');
		},
		function(){
			var info = $x('id("userInfoDiv")//a[last()]');
			
			postVideo({
				'post[two]' : context.title.match(/ - (.*)/)[1] + ' (via ' + A(info.href, info.textContent) + ')',
			});
		}
	],
	
	// Video - Google Video
	[
		function(){
			return location.hostname.match('video.google.com');
		},
		function(){
			postVideo({
				'post[two]' : context.title.match(/(.*) - /)[1],
			});
		}
	],
	
	// Link
	[
		function(){
			// ELSE
			return true;
		},
		function(){
			var url = location.href;
			postTumblr({
				'post[type]' : 'link',
				'post[one]' : context.title,
				'post[two]' : url,
				'post[three]' : LINK_ADD_THUMBNAIL ? '<img src="http://mozshot.nemui.org/shot?'+url+'" />' : '',
			});
		}
	],
].some(function(reg){
	if(reg[0]()){
		reg[1]();
		return true;
	}
})

// ----[Greasemonkey]-------------------------------------------------
function GM_xmlhttpRequest(opt){
	var req = new XMLHttpRequest();
	var createHandler = function(event, callback){
		if(!callback)
			return;
		
		req[event] = function(){
			var completed = false;
			try{
				if(req.readyState==4 && req.status)
					completed = true;
			}catch(e){
				// ドメインの取得に失敗すると、req.statusでエラーが起きる
			}
			callback({
				responseText : req.responseText,
				readyState : completed? req.readyState : 0,
				responseHeaders : completed? req.getAllResponseHeaders() : '',
				status : (completed? req.status : 0),
				statusText : (completed? req.statusText : '')
			});
		}
	}
	createHandler('onload', opt.onload);
	createHandler('onerror', opt.onerror);
	req.open(opt.method, opt.url, true);
	for (var prop in opt.headers) {
		req.setRequestHeader(prop, opt.headers[prop]);
	}
	req.send(opt.data);
}


// ----[JSActions]-------------------------------------------------
function addTab(url, onload){
	var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
		.getService(Components.interfaces.nsIWindowMediator);
	var topWindowOfType = windowManager.getMostRecentWindow('navigator:browser');
	var browser = topWindowOfType.document.getElementById('content');
	var tab = browser.addTab(url)
	if(onload){
		var browser = tab.linkedBrowser;
		browser.addEventListener('DOMContentLoaded', function(event){
			browser.removeEventListener('DOMContentLoaded', arguments.callee, true);
			onload(event.originalTarget.defaultView);
		}, true);
	}
	return tab;
}

function convertCharCodeTo(str, charset){
	var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
		.getService(Components.interfaces.nsIScriptableUnicodeConverter);
	try{
		converter.charset = charset;
		return converter.ConvertToUnicode(str);
	}catch(e){}
}

function J(str){
	return convertCharCodeTo(str, 'UTF-8');
}

function log(str){
	return _jsaCScript.dump(str);
}

// ----[Application]-------------------------------------------------
function reblog(id){
	var url = 'http://www.tumblr.com/reblog/' + id;
	
	// Ctrlキー押下でフォーム画面へ遷移
	if(CTRL_KEY & context.withKey){
		addTab(url + '?r=dashboard');
		return;
	}
	
	var onerror = function(res){
		addTab(url);
		alert('Post failed. (' + res.status + ')');
	}
	
	getInputFields(url, function(fields){
		if(fields['post[type]']=='photo'){
			var source = {
				url : url,
				title : 'ReBlog: ' + id,
			}
			
			checkPhoto(function(){
				postByXHR(url, fields, null, onerror);
			}, source);
			
			return;
		}
		
		postByXHR(url, fields, null, onerror);
	}, onerror)
}

function postTumblr(params){
	params = update({
		'post[two]' : A(location.href, context.title || 'no title'),
		source : 'bookmarklet',
	}, params)
	
	// Shiftキー押下でタイトル簡易編集
	if(SHIFT_KEY & context.withKey){
		if((params['post[two]'] = prompt('Title' + repeat('_', 200), params['post[two]'])) == null)
			return;
	}
	
	// Ctrlキー押下でフォーム画面へ遷移
	if(CTRL_KEY & context.withKey){
		var type = params['post[type]'];
		addTab('http://www.tumblr.com/new/' + type, function(win){
			var doc = win.document;
			for(var name in params){
				var elm = doc.getElementsByName(name)[0];
				if(elm)
					elm.value=params[name];
			}
			
			var setDisplay = function(id, style){
				doc.getElementById(id).style.display = style;
			}
			switch(type){
			case 'photo':
				setDisplay('photo_upload', 'none');
				setDisplay('photo_url', 'block');
				break;
			case 'link':
				setDisplay('add_link_description', 'none');
				setDisplay('link_description', 'block');
				break;
			}
		});
		return;
	}
	
	var onerror = createReopener('Post failed.');
	var onload = function(res){
		if(!res.responseText.match('Tumblr: Share'))
			onerror(res);
	}
	postByXHR('http://www.tumblr.com/publisher/save', params, onload, onerror);
}

function createReopener(msg1, source){
	source = source || {
		url : location.href,
		title : context.title,
	}
	return function(res, msg2){
		msg2 = msg2 || '';
		if(confirm(
			msg1 + msg2 + '\n'+
			'Will you reopen?\n\n' + 
			'Source page: ' + source.title + '\n' + source.url)){
			addTab(source.url);
		}
	}
}

function checkPhoto(proceed, source){
	var onerror = createReopener('Post failed.', source);
	var onlimit = createReopener("You've used 100% of your daily photo uploads.", source);
	getInputFields('http://www.tumblr.com/new/photo', function(fields){
		if(isEmpty(fields))
			return onerror();
		
		if(!('photo_src' in fields))
			return onlimit();
		
		proceed();
	}, onerror);
}

function postPhoto(params){
	for(var i=0 ;  i<IMAGE_PROTECTED_SITES.length ; i++){
		if(
			params.photo_src.match(IMAGE_PROTECTED_SITES[i]) && 
			!confirm('This image is being protected maybe.\nWill you continue to post?')){
			
			return;
		}
	}
	
	if(POST_TO_FFFFOUND)
		postFFFFOUND(params);
	
	checkPhoto(function(){
		postTumblr(update({
			'post[type]' : 'photo',
		}, params));
	});
}

function postVideo(params){
	var tag = 'post[one]';
	params[tag] = adjustEmbed(params[tag]) || location.href;
	
	postTumblr(update({
		'post[type]' : 'video',
	}, params));
}

function getInputFields(url, onload, onerror){
	getByXHR(
		url, 
		null, 
		function(res){
			var doc = convertToHTMLDocument(res.responseText);
			var elms = $x('id("edit_post")//*[name()="INPUT" or name()="TEXTAREA"]', doc, true);
			var fields = {};
			elms.forEach(function(elm){
				fields[elm.name] = elm.value;
			});
			return onload(fields);
		}, 
		onerror
	)
}

function adjustEmbed(tag){
	if(!tag)
	 return;
	
	var ratio;
	return tag.replace(/(width=")(.*?)(")/g, function(w,p,n,s){
		ratio = THEME_IMAGE_WIDTH / n;
		return p + THEME_IMAGE_WIDTH + s;
	}).replace(/(height=")(.*?)(")/g, function(w,p,n,s){
		return p + Math.floor(n*ratio) + s;
	});
}

function amazon(asin, onload, onerror){
	onerror = onerror || function(res){
		alert("Can't get product information. ("+res.status+')');
	};
	
	getByXHR(
		'http://webservices.amazon.co.jp/onca/xml', {
			Service        : 'AWSECommerceService',
			SubscriptionId : '0DCQFXHRBNT9GN9Z64R2',
			Operation      : 'ItemLookup',
			ResponseGroup  : 'Small,Images',
			ItemId         : asin,
		}, 
		function(res){
			var xml = convertToXML(res.responseText);
			if(xml.Error.length())
				return onerror(res);
			var item = xml.Items.Item;
			
			function Image(img){
				if(!img.length())
					return;
				
				return {
					get URL(){
						return ''+img.URL;
					},
					get w(){
						return 1*img.Width;
					},
					get h(){
						return 1*img.Height;
					},
				}
			}
			
			onload({
				get title(){
					return ''+item.ItemAttributes.Title;
				},
				get creators(){
					var creators = [];
					
					// '原著'以外
					for each(var creator in item.ItemAttributes.Creator.(@Role != '\u539F%u8457'))
						creators.push(''+creator);
					return creators;
				},
				get largestImage(){
					return this.largeImage || this.mediumImage || this.smallImage;
				},
				get largeImage(){
					return new Image(item.LargeImage);
				},
				get mediumImage(){
					return new Image(item.MediumImage);
				},
				get smallImage(){
					return new Image(item.SmallImage);
				},
			});
		}, 
		onerror
	)
}


// ----[FFFFOUND!]-------------------------------------------------
function iLoveThis(id){
	var onerror = function(res){
		alert('FFFFOUND!\nError: ' + res.status);
	}
	var onload = function(res){
		if(res.responseText.match('"error":"(.*?)"'))
			alert('FFFFOUND!\nError: ' + RegExp.$1);
	}
	postByXHR('http://ffffound.com/gateway/in/api/add_asset', {collection_id:id}, onload, onerror);
}

function postFFFFOUND(params){
	if(location.href.match('http://reader.livedoor.com/reader/'))
		return;
	
	if(location.href.match('http://ffffound.com/')){
		var id = context.target.id.match(/\d+/)[0];
		iLoveThis(id);
		return;
	}
	
	getByXHR('http://ffffound.com/bookmarklet.js', null, function(res){
		var token = res.responseText.match(/token='(.*?)'/)[1];
		var onerror = createReopener('FFFFOUND!\n');
		var onload = function(res){
			if(res.responseText.match('(FAILED:|ERROR:) (.*?)</span>')){
				if(RegExp.$2 != 'The image you choose is already in your found.'){
					onerror(res, RegExp.$2 + '\n');
				}
			}
		}
		getByXHR('http://ffffound.com/add_asset', {
			token : token,
			url : params.photo_src,
			referer : location.href,
			title : context.title,
		}, onload, onerror)
	});
}

// ----[Utility]-------------------------------------------------
function getComputedStyle(elm, css){
	return window.getComputedStyle(elm, '').getPropertyValue(css);
}

function getElementByPosition(x, y){
	return Components.classes['@mozilla.org/accessibilityService;1'].
		getService(Components.interfaces.nsIAccessibilityService).
		getAccessibleFor(document).
		getChildAtPoint(x, y).
		QueryInterface(Components.interfaces.nsIAccessNode).
		DOMNode;
}

function tagName(elm){
	return elm.tagName? elm.tagName.toLowerCase() : '';
}

function repeat(s, n){
	return new Array(n+1).join(s);
}

function isEmpty(obj){
	for(var i in obj)
		return false;
	return true;
}

function bind(obj, func) {
	func = (func instanceof Function)? func : obj[func];
	return function() {
		func.apply(obj, arguments);
	}
}

function update(obj, params){
	if(obj.setAttribute){
		for(var key in params)
			obj.setAttribute(key, params[key]);
	} else {
		for(var key in params)
			obj[key] = params[key];
	}
	return obj;
}

function A(href, text){
	return '<a href="' + href + '">' + text + '</a>';
}

function extract(str, re, group){
	group = group==null? 1 : group;
	var res = str.match(re);
	return res ? res[group] : '';
}

// ----[DOM]-------------------------------------------------
function convertToHTMLDocument(html) {
	var xsl = (new DOMParser()).parseFromString(
		'<?xml version="1.0"?>\
			<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">\
			<output method="html"/>\
		</stylesheet>', "text/xml");
	
	var xsltp = new XSLTProcessor();
	xsltp.importStylesheet(xsl);
	
	var doc = xsltp.transformToDocument(document.implementation.createDocument("", "", null));
	doc.appendChild(doc.createElement("html"));
	
	var range = doc.createRange();
	range.selectNodeContents(doc.documentElement);
	doc.documentElement.appendChild(range.createContextualFragment(html));
	
	return doc
}

function convertToXML(text){
	return new XML(text.replace(/<\?.*\?>/gm,'').replace(/<!.*?>/gm, '').replace(/xmlns=".*?"/,''));
}

function queryString(params, question){
	if(isEmpty(params))
		return '';
	
	var qeries = [];
	for(var key in params)
		qeries.push(key + '='+ encodeURIComponent(params[key]));
	return (question? '?' : '') + qeries.join('&');
}

function getByXHR(url, params, onload, onerror){
	url = url + queryString(params, true);
	GM_xmlhttpRequest({
		method : 'GET',
		url : url,
		onload : function(res){
			if(res.status!=200)
				return onerror(res);
			onload && onload(res);
		},
		onerror : onerror,
	})
}

function postByXHR(url, params, onload, onerror){
	GM_xmlhttpRequest({
		method : 'POST',
		url : url,
		data : queryString(params),
		onload : function(res){
			if(res.status!=200)
				return onerror(res);
			onload && onload(res);
		},
		onerror : onerror,
		headers : {
			'Content-Type' : 'application/x-www-form-urlencoded',
		}
	})
}

function appendFields(form, params){
	var doc = form.ownerDocument;
	var df = doc.createDocumentFragment();
	
	for(var name in params){
		df.appendChild(update(doc.createElement('input'), {
			type : 'text',
			name : name,
			value : params[name],
		}));
	}
	
	form.appendChild(df);
	
	return form;
}

// originate at cho45 - http://lowreal.net/
function $x(exp, context, multi) {
	if(typeof(unsafeWindow)!='undefined'){
		Node = unsafeWindow.Node;
	}
	
	if (!context) context = document;
	var resolver = function (prefix) {
		var o = document.createNSResolver(context)(prefix);
		return o ? o : (document.contentType == "text/html") ? "" : "http://www.w3.org/1999/xhtml";
	}
	var exp = document.createExpression(exp, resolver);
	var value = function(node){
		if(!node)
			return;
		
		switch (node.nodeType) {
		case Node.ELEMENT_NODE:
			return node;
		case Node.ATTRIBUTE_NODE:
		case Node.TEXT_NODE:
			return node.textContent;
		}
	}
	
	var result = exp.evaluate(context, XPathResult.ANY_TYPE, null);
	switch (result.resultType) {
		case XPathResult.STRING_TYPE : return result.stringValue;
		case XPathResult.NUMBER_TYPE : return result.numberValue;
		case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
		case XPathResult.UNORDERED_NODE_ITERATOR_TYPE: {
			if(!multi)
				return value(result.iterateNext());
			
			result = exp.evaluate(context, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
			var ret = [];
			for (var i = 0, len = result.snapshotLength; i < len ; i++) {
				ret.push(value(result.snapshotItem(i)));
			}
			return ret;
		}
	}
	return null;
}
