Amazon Local Currency - Dynamic version

By Ori Avtalion Last update May 2, 2009 — Installed 5,156 times. Daily Installs: 15, 2, 4, 6, 8, 7, 3, 10, 3, 4, 5, 3, 10, 5, 3, 5, 6, 2, 3, 6, 3, 5, 5, 5, 13, 12, 3, 3, 6, 5, 6, 3

There are 2 previous versions of this script.

/*
  GreaseMonkey userscript for showing prices in your local currency.
  Customize to your own currency below.

  Exchange rates provided by Yahoo @ www.yahoo.com

  New to GreaseMonkey? Visit <http://www.greasespot.net/>

  2005-04-17  Carl Henrik Lunde  chlunde+greasemonkey <at> ping.uio.no
              http://www.ping.uio.no/~chlunde/stuff

  Maintainer:
    Ori Avtalion  ori <at> avtalion.name

  Contributors:
    Simon Pope skjpope -> gmail.com

  Changelog:

  2005-09-14
    * Added GM menu options to change the local currency coin and
      symbol.
    * Added price rounding.
    * Added option to toggle whether or not to display the
      local currency symbol.
    * Fixed double printing of converted currency

  2005-10-08
    * Fixed occasions where the price would show up several times
    * Prices are now converted even when they're part of a large sentence,
      such as in the check-out process ("total price is $42.13")

  2006-02-25
    * Added handling for prices that are not the first element in a tag,
      such as in amazon UK's wish list: <span><b>Price:</b>$13.37</span>

  2008-03-14
    * The converted price now appears right next to the original price.
    * All of the prices in a piece of text are converted, instead of just the first one.

  2008-15-12
    * Added support for Amazon.ca
    * The regex now matches 0 or more spaces after the currency symbol. Useful for amazon.ca
	  (I could just add the single space to the canadian dollar currency regex pattern, but
	  other websites might behave differently).

  2009-05-02
    * Added support for Amazon.de (Euros).
    * Refactored a bit to allow for different price parsers.

  TODO:
    * Add GM menu options to change source currency
    * Add option and GUI to choose whether the local currency symbol
      should be prefixed or suffixed to the currency
	* Figure out the local currency automatically, so one would be able to use this script
	  on every website, even if it's using GBP on a .com domain
*/

// ==UserScript==
// @name          Amazon Local Currency - Dynamic version
// @namespace     http://userscripts.org/scripts/show/1476
// @description   Show prices in you local currency
// @include       http://www.amazon.com/*
// @include       https://www.amazon.com/*
// @include       http://amazon.com/*
// @include       https://amazon.com/*
// @include       http://www.amazon.co.uk/*
// @include       https://www.amazon.co.uk/*
// @include       http://amazon.co.uk/*
// @include       https://amazon.co.uk/*
// @include       http://www.amazon.ca/*
// @include       https://www.amazon.ca/*
// @include       http://amazon.ca/*
// @include       https://amazon.ca/*
// @include       http://www.amazon.de/*
// @include       https://www.amazon.de/*
// @include       http://amazon.de/*
// @include       https://amazon.de/*
// ==/UserScript==

(function() {

// Helper function. From the Prototype Javascript Framework:
String.prototype.endsWith = function (pattern) {
	var d = this.length - pattern.length;
	return d >= 0 && this.lastIndexOf(pattern) === d;
};

var amazonCurrencies = ["USD", "GBP", "CAD"];
var currencyFrom;


// Decimal separator:   .
// Thousands separator: ,
function regularPriceParser(price, currency) {
	return parseFloat(price.replace(/,/g, ""));
}

// Decimal separator:   ,
// Thousands separator: .
function europeanPriceParser(price, currency) {
	function replacer(character) {
		var translate = {".":"", ",":"."};
		return translate[character];
	}
	return regularPriceParser(price.replace(/[,.]/g, replacer), currency);
}

// priceRegex explanation:
// Match a string that begins with the symbol, and then
// has 0 or more spaces, digits, commas and periods, ending with a digit.
// The actual numeric price portion MUST BE enclosed in parentheses.

var currencies = {
	"USD" : {
		symbol: "$",
		priceRegex: /\$\s*([\d,.]+\d)/,
		parser: regularPriceParser
	},

	"GBP" : {
		symbol: "£",
		priceRegex: /£\s*([\d,.]+\d)/,
		parser: regularPriceParser
	},

	"CAD" : {
		symbol: "CDN$",
		priceRegex: /CDN\$\s*([\d,.]+\d)/,
		parser: regularPriceParser
	},

	"EUR" : {
		symbol: "EUR",
		priceRegex: /EUR\s*([\d,.]+\d)/,
		parser: europeanPriceParser
	}
};

// Check which Amazon we're at:
// amazon.com
if (document.domain.endsWith("com")) {
	currencyFrom = "USD";
// amazon.co.uk
} else if (document.domain.endsWith("co.uk")) {
	currencyFrom = "GBP";
// amazon.ca
} else if (document.domain.endsWith("ca")) {
	currencyFrom = "CAD";
// amazon.de
} else if (document.domain.endsWith("de")) {
	currencyFrom = "EUR";
} else {
	return;
}

// Configuration keys (not all of them)
var LAST_RUN = "last_run_";
var CURRENCY_RATE = "currency_rate_";

// Customize to fit:
// (Some options are modifiable from the GUI)
var currencyToDefault = "ILS";
var currencyToSymbolDefault = "NIS ";
var decimalPlaces = 2;
var prefixCurrencySymbol = true;

// Only traverse these elements
var elnames = ["td", "font", "b", "span", "strong", "div"];

var rounding = Math.pow(10, decimalPlaces);

// Check last run time
var rate = GM_getValue(CURRENCY_RATE + currencyFrom);
var lastRun = GM_getValue(LAST_RUN + currencyFrom, "01/01/0001");
var currencyTo = GM_getValue("currency_to", currencyToDefault);
var todayDate = new Date();
var todayString = todayDate.getDate() + "/" + todayDate.getMonth() + "/" + todayDate.getFullYear();
var currencyToSymbol = GM_getValue("currency_symbol", currencyToSymbolDefault);

// Function definitions

function fetchCurrencyData(coin, callback) {
	GM_xmlhttpRequest({
		method: "GET",
		url: "http://download.finance.yahoo.com/d/quotes.csv?s=" + coin + currencyTo + "=X&f=l1&e=.csv",
		onload: function(responseDetails) {
			var rate = responseDetails.responseText.replace(/[\r\n]/g, "");
			GM_setValue(CURRENCY_RATE + coin, rate);
			GM_setValue(LAST_RUN + coin, todayString);
			callback();
		},
		onerror: function(responseDetails) {
			alert("Error fetching currency data for " + coin);
		}
	});
}

// Receives a price, and converts it. Returns "<original price> (<converted price>)"
function appendConversion(price, matched) {
	var originalPrice = currencies[currencyFrom].parser(matched, currencyFrom);

	if (isNaN(originalPrice)) {
		return price;
	}

	var converted = formatCurrency(originalPrice * rate, rounding,
		currencyToSymbol, prefixCurrencySymbol);

	return price + " (" + converted + ")";
}

function formatCurrency(num, rounding, symbol, prefix) {
	sign = (num == (num = Math.abs(num)));
	num = Math.floor(num*rounding + 0.50000000001);
	cents = num % rounding;

	num = Math.floor(num / rounding).toString();

	if (cents < 10) {
		cents = "0" + cents;
	}

	for (var i = 0; i < Math.floor((num.length - (1 + i)) / 3); i++) {
		num = num.substring(0, num.length - (4 * i + 3)) + ',' +
		                       num.substring(num.length-(4*i+3));
	}

	if (prefix) {
		return (symbol + ((sign)?'':'-') + num + '.' + cents);
	} else {
		return (((sign)?'':'-') + num + '.' + cents + symbol);
	}
}

// Convert desired currency
function convertCurrency() {

	// Match a string that begins with the symbol, and then
	// has 0 or more spaces, digits, commas and periods, ending with a digit
	var currency = currencies[currencyFrom];

	var i,j,k;

	for (i = 0; i < elnames.length; ++i) {
		var elems = document.getElementsByTagName(elnames[i]);

		for (j = 0; j < elems.length; ++j) {
			var price = elems[j];

			for (k = 0; k < price.childNodes.length; ++k) {
				var currNode = price.childNodes[k];
				// Only modify text nodes
				if (currNode.nodeType == 3) {

					// Quick check before using the regex
					if (currNode.nodeValue.indexOf(currency.symbol) != -1) {
						// nbsp replacement done to fix some amazon.de prices (e.g. "EUR&nbsp;1,23")
						GM_log(currNode.nodeValue.replace(/&nbsp;/, " "));
						currNode.nodeValue = currNode.nodeValue.replace(/&nbsp;/, " ").replace(currency.priceRegex, appendConversion);
					}
				}
			}
		}
	}
}


function setLocalCurrency() {
	var newCurrencyTo = prompt("Enter the code for your local currency (e.g. AUD, USD, ILS, etc.)", "");

	if (newCurrencyTo === "" || newCurrencyTo === null) {
		alert("Currency code is invalid. Please enter again");
		return;
	}

	// GM_log("Currency changed from " + currencyTo + " to " + newCurrencyTo);

	GM_setValue("currency_to", newCurrencyTo);
	currencyTo = newCurrencyTo;

	// Reset the various conversion rates
	for (var i = 0; i < amazonCurrencies.length; ++i) {
		GM_setValue(LAST_RUN + amazonCurrencies[i], "01/01/0001");
	}

	// Not really.. at this point, the fetching isn't done yet
	alert("Success! Refresh page to see the changes.");
}

function setLocalCurrencySymbol() {
	var newSymbol = prompt("Enter the symbol for your local currency ( e.g. A$, $, ¥, £, etc.)", "");

	if (newSymbol === '' || newSymbol === null) {
		alert("Symbol is invalid. Please enter again");
		return;
	}

	alert("Success! Refresh page to see the changes.");

	// GM_log("Currency Symbol changed from " + currencyToSymbol + " to " + newSymbol);

	GM_setValue("currency_symbol", newSymbol);
	currencyToSymbol = newSymbol;
}


GM_registerMenuCommand("Change Local Currency (" + currencyTo + ")", setLocalCurrency);
GM_registerMenuCommand("Change Local Currency Symbol (" + currencyToSymbol + ")", setLocalCurrencySymbol);


if (rate === undefined || todayString !== lastRun) {
	// GM_log("Currency data is out-dated. Fetching new information...");
	fetchCurrencyData(currencyFrom, function() {
		rate = GM_getValue(CURRENCY_RATE + currencyFrom);
		convertCurrency();
	});
} else {
	convertCurrency();
}

})();