Amazon Lowest Price CheckerのSleipnir版

http://d.hatena.ne.jp/gigi-net/20090421/1240324155

 今さら説明するまでもないかもしれないけれど、アマゾンの商品ページを開いたときにカカクコムの最安価格と比較し、どちらが安いのかを表示してくれるスクリプト

 Firefoxもインストールしているものの、メインブラウザはSleipnirなのでそのままでは使えず、仕方がないので自分で何とかしてみた。

 とは言っても一から作り直したわけではなく、Sleipnir(というかTrident)では使えない機能を別の物に置き換えただけ。

 変更点は、大きく分けて以下の通り。

GM_xmlhttpRequestの追加
GM_xmlhttpRequest関数が存在しないため、ActiveXを利用して通信
class属性
<div class="hoge"> などとしたとき、getAttribute("class")では取得できないためgetAttribute("className")に変更
DOMParser
new DOMParser()が使えないために他のを探したが、探すのに時間がかかって面倒になり、そういうのを使うまでもないか?と思ったんで正規表現で代用


 一つ目。

 GM_xmlhttpRequestは、ここにあるLDR Full Feed for SeaHorseの中の物をほぼそのまま使用。

 ただ、何故か同期通信でやると価格が表示されないので、仕方なく非同期通信を使った。普通こういうのは同期通信でやるんだろうけどなあ。

 スクリプトの最後にalertを入れると同期通信でもちゃんと表示されるってことは、メインのスクリプトが終わった時点で全部が強制終了されてしまうからなんだろうか。

 JavaScriptにwait関数が無いのが辛い。


 二つ目。

 まあそのまま。

 class="hoge" としてるんだから "class" で取得できるのが普通だと思うんだけど、Tridentでは何故か出来ない。

 有名な話なのかもしれないけど、今回初めて知った。


 三つ目。

 今もう一度調べてみたら、parseFromStringだけならActiveXMSXML.DOMDocumentを利用して簡単に使えそう。

 その要素に対するgetElementsByTagNameなんかもそのまま使えるんだろうか。

 まあとりあえず今はこのままでいいや。


 細かいところは色々書き換えてあるけれど、Sleipnirで動かすために必須の変更点というのはこれぐらい。

 機能アップというか、型番取得の手法を一つ追加。

 とは言ってもASINでconecoから引っ張ってくるアレじゃないのであしからず。


 というわけで、以下のスクリプトを Sleipnir2\plugins\seahorse に "適当な名前.user.js" で保存すればOK。当然、要Seahorse

// ==UserScript==
// @name           Amazon Lowest Price Checker
// @namespace      http://gigi-net.net
// @include        http://www.amazon.co.jp/*
// @type           SleipnirScript
// ==/UserScript==
(function(){
var d = document;

if(V(d,'ID("buyboxTable")')==null) return;

function V(pre,p /*,value*/) {
	var s = p;
	var cls = "class";

	if(/*@cc_on!@*/false) cls = "className";

	s = s.replace(/\bID\s*\(/g,"getElementById(");
	s = s.replace(/\bTAG\s*\(/g,"getElementsByTagName(");
	s = s.replace(/\bNAME\s*\(/g,"getElementsByName(");
	s = s.replace(/\bATTR\s*\(\s*(["'])class(Name)?\1/g,'getAttribute("'+cls+'"');

	if(arguments.length >= 3) {
		try {
			eval("pre."+s+"=arguments[2]");
		} catch(e) {
			throw e;
		}
	} else {
		try {
			var v = eval("pre."+s);
		} catch(e) {
			throw e;
		}
		return v;
	}
}

//価格を3ケタ区切りにする関数
function SetPrice(price){
	var num = new String(price).replace(/,/g, "");
	while(num != (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2")));
	return num;
}


//価格を数値に変換
function ConvertPrice(price){
	price = price.replace(/-.+$/,"");
	return parseInt(price.replace(/\D/g,""));
}

if(typeof GM_xmlhttpRequest == 'undefined')
GM_xmlhttpRequest = function(opt) {
	var http = null;

	if(typeof ActiveXObject != 'undefined') {
		try{
			http = new ActiveXObject("Msxml2.XMLHTTP");
		} catch(e){
			try{
				http = new ActiveXObject("Microsoft.XMLHTTP");
			} catch(e){
				return;
			}
		}
	} else if(window.XMLHttpRequest) {
		http = new XMLHttpRequest();
	} else return;
	http.open(opt.method.toUpperCase(), opt.url, false);
	for (var i in opt.headers) {
	    if (!opt.headers.hasOwnProperty(i)) continue;
		http.setRequestHeader(i, opt.headers[i]);
	}
	http.onreadystatechange = function() {
		if(http.readyState == 4){
			if(http.status == 200){//succeed
				opt.onload(http);
			} else {
				opt.onerror(http);
			}
			http = null;
		}
	};
	http.send(null);
}

//APIURL定義
var api_url ="http://api.kakaku.com/Ver1.1/ItemSearch.aspx";

//製造元リファレンス: EYE-FI-4GB-J 

//ロード中メッセージ表示
var title = V(d,'TAG("h1")');
var check_lowest = d.createElement("div");
check_lowest.innerHTML = "<img src='http://img.f.hatena.ne.jp/images/fotolife/g/gigi-net/20090421/20090421143419.gif?1240292104' style='vertical-align:middle'>価格.comから最低価格を読み込んでいます。";
title[0].parentNode.appendChild(check_lowest);

	//製品型番を取得
var kataban = "";
try{
	var dom_kataban = V(d,'ID("productDetailsDiv").childNodes[0]');
	for(var i=0,l=dom_kataban.childNodes.length;i<l;i++){
		if(dom_kataban.childNodes[i].innerHTML.indexOf("メーカー型番") != -1){
			kataban = dom_kataban.childNodes[i].innerHTML;
			break;
		}
	}
	if(kataban == "") throw "";
}catch(e){
	try {
		var DOM_link = V(d,'ID("productDetails")');

		for(var i=0;i<10;i++) {
			DOM_link = DOM_link.nextSibling;
			if(DOM_link == null) throw "";
			if(DOM_link.nodeName == 'TABLE') break;
		}
		var LI = V(DOM_link,'TAG("tbody")[0].TAG("tr")[0].TAG("td")[0].TAG("div")[0].TAG("ul")[0].TAG("li")');
		for(var i=0,l=LI.length;i<l;i++){
			if(LI[i].innerHTML.indexOf("製造元リファレンス") != -1 ||
			   LI[i].innerHTML.indexOf("メーカー型番") != -1){
				kataban = LI[i].innerHTML;
				break;
			}
		}
	
	}catch(e){}
}
if(kataban != ""){
	kataban = kataban.replace(/<.*>\s*(.+?)\s*$/,"$1");
}else{
	check_lowest.innerHTML = "<b>Error:</b>商品の型番が取得できませんでした。";
	title[0].parentNode.appendChild(check_lowest);
}
//価格comAPIを用いて型番から最安値を取得
xml_url = api_url +"?Keyword="+encodeURIComponent(kataban)+"&CategoryGroup=ALL&SortOrder=pricerank&HitNum=5";

//Amazon.comの価格を取得
var ap = 0;
try{
	var table = V(d,'ID("buyboxPriceBlock").TAG("table")[0].TAG("tbody")[0].TAG("tr")[0].TAG("td")');
	if(V(table[0],'TAG("b")[1].ATTR("class")') == "price"){
		ap = V(table[0],'TAG("b")[1].innerHTML');

	}else if(V(table[0],'TAG("span")[0].ATTR("class")') == "price"){
		ap = V(table[0],'TAG("span")[0].innerHTML');
	}
}catch(e){
	try{
		var amazon_price = V(d,'ID("priceBlock").TAG("table")[0].TAG("tbody")[0].TAG("tr")');

		for(var i=0;i<amazon_price.length;i++){
			var table = V(amazon_price[i],'TAG("td")');
			if(table[0].innerHTML.indexOf("価格") == 0){
				try {
					if(V(table[1],'TAG("b")[0].ATTR("class")') == "priceLarge"){
						ap = V(table[1],'TAG("b")[0].innerHTML');
					}
				} catch(e) {
					if(V(table[1],'TAG("span")[0].ATTR("class")') == "priceLarge"){
						ap = V(table[1],'TAG("span")[0].innerHTML');
					}
				}
			}
		}
	}catch(e){
		var ap = 0;
	}
}
ap = ConvertPrice(""+ap);

//価格表示関数
function ShowPrice(x){
	var res = x.responseText;
	if(res.indexOf("<LowestPrice>") == -1){

		check_lowest.innerHTML="<b>Error:</b>価格.comで該当商品が見つかりませんでした。";
	}else{
		res.match(/<LowestPrice>(.+?)<\/LowestPrice>/);
		price = RegExp.$1;
		res.match(/<ItemPageUrl>(.+?)<\/ItemPageUrl>/);
		var pageurl = RegExp.$1;
		var sa = ap - price;
		check_lowest.innerHTML ="<b>最低価格:<span class='priceLarge'> &yen;  "+SetPrice(price)+"</span></b>";
		if(sa > 0 && ap != 0 && sa < 100000){
			check_lowest.innerHTML += " <font size=3>  Amazonより<span class='priceLarge'> &yen; "+SetPrice(sa)+"</span>安く買えます。</font>";
		}
		check_lowest.innerHTML +="<font size=3><a target='_blank' href="+pageurl+">価格.comを見る</a></font>";
		check_lowest.style.fontSize ="18px";
	}
	title[0].parentNode.appendChild(check_lowest);
}

//APIからXMLを読み込んで表示する。
if(kataban != ""){
	GM_xmlhttpRequest({
	  method:"GET", 
	  url:xml_url,
	  onload:ShowPrice,
	  onerror:function(){}
	});
}

//著作表示
var powered =d.createElement("div");
powered.innerHTML +=" <center>powered by <a href ='http://kakaku.com/'>価格.com</a></center>";
d.body.appendChild(powered);

})();

 ほんとはOperaにも…と思ったけど、他ドメインにアクセスする方法が分からなかったので断念。