買ったアイテムの合計金額を表示するGreasemonkeyスクリプト
インストールすると日付の横に「合計金額を計算」というリンクが現れる。それをクリックすると、その日の日記に含まれるアイテムの金額の合計が計算される。
// ==UserScript== // @name How much money do I waste? // @namespace http://d.hatena.ne.jp/nozom/ // @include http://d.hatena.ne.jp/* // ==/UserScript== (function(){ var item_price = {}; var item_title = {}; var req = {}; function number_to_human_readable(n) { var num = new String(n); while (num != (num = num.replace(/^(-?\d+)(\d{3})/, "$1,$2"))) {}; return num; } function getElementsByXPath(xpath, doc, context) { if (doc == null) doc = document; if (context == null) context = doc; var it = doc.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); var nodes = new Array(); var node; while ((node = it.iterateNext()) != null) { nodes.push(node); } return nodes; } function get_asin_from_link(link) { if (link.match(/\/(ASIN|asin|asininfo)\/([^\/]+)/)) return RegExp.$2; if (link.match(/\/gp\/product\/([^\/]+)/)) return RegExp.$1; if (link.match(/\/dp\/product\/([^\/]+)/)) return RegExp.$1; return null; } function get_title(doc) { var title = doc.getElementsByTagName('title')[0]; if (title) { return title.textContent; } else { return null; } } function get_amazon_price(doc) { var nodes = doc.getElementsByTagName('li'); for (var i = 0; i < nodes.length; i++) { if (nodes[i].className == 'price' && nodes[i].textContent.match(/Amazon価格:/)) { var node = nodes[i].getElementsByTagName('strong')[0]; if (node) { var price = node.textContent.replace(",", ""); return parseInt(price); } } } return null; } var handlers = []; function add_handler(handler) { handlers.push(handler); } function call_handlers() { var new_handlers = []; for (var i = 0; i < handlers.length; i++) { var handler = handlers[i]; if (!handler()) { new_handlers.push(handler); } } handlers = new_handlers; } function new_request(asin) { req[asin] = GM_xmlhttpRequest({ method: 'GET', url: "http://d.hatena.ne.jp/asin/" + asin, onload: function(req) { var d = document.createElement('div'); d.innerHTML = req.responseText; item_price[asin] = get_amazon_price(d); item_title[asin] = get_title(d); req[asin] = null; call_handlers(); }, }); } function update_label(a, n) { a.textContent = "合計金額を計算中... (残り: " + n + ")"; } function create_result_node(total_price, n, errors) { var span = document.createElement('span'); span.style.fontWeight = 'normal'; span.style.fontSize = '80%'; span.style.float = 'right'; var text = "合計金額 (" + n + "点): " + number_to_human_readable(total_price) + "円"; span.appendChild(document.createTextNode(text)); if (errors.length > 0) { span.appendChild(document.createTextNode(" (エラー: ")); for (var i = 0; i < errors.length; i++) { if (i > 0) { span.appendChild(document.createTextNode(", ")); } var asin = errors[i]; var a = document.createElement('a'); a.title = item_title[asin]; a.href = "http://d.hatena.ne.jp/asin/" + asin; a.textContent = asin; span.appendChild(a); } span.appendChild(document.createTextNode(")")); } return span; } function on_calc_button_clicked(ev) { var a = ev.target; var divDay = a.parentNode.parentNode; var nodes = divDay.getElementsByTagName("a"); var asinHash = {}; for (var i = 0; i < nodes.length; i++) { var asin = get_asin_from_link(nodes[i].href); if (asin) { asinHash[asin] = 1; } } var n = 0; for (var asin in asinHash) { n++; } update_label(a, n); add_handler(function() { var n = 0; var errors = []; var wait = 0; var total_price = 0; for (var asin in asinHash) { if (typeof item_price[asin] == 'undefined') { GM_log("waiting: " + asin); wait++; } else if (item_price[asin] == null) { errors.push(asin); } else { n++; total_price += item_price[asin]; } } if (wait > 0) { update_label(a, wait); return false; } var span = create_result_node(total_price, n, errors); a.parentNode.appendChild(span); a.parentNode.removeChild(a); return true; }); var asinList = []; for (var asin in asinHash) { if (!item_price[asin] && !req[asin]) { new_request(asin); } } ev.preventDefault(); return; } function add_calc_button(h2) { var a = document.createElement("a"); a.href = "#"; a.appendChild(document.createTextNode("合計金額を計算")); a.style.fontWeight = 'normal'; a.style.fontSize = '80%'; a.style.float = 'right'; a.addEventListener('click', on_calc_button_clicked, false); h2.appendChild(a); } var divDayHeadings = getElementsByXPath('//div[@class="hatena-body"]//div[@id="days"]/div[@class="day"]/h2'); for (var i = 0; i < divDayHeadings.length; i++) { add_calc_button(divDayHeadings[i]); } })()