BvS BillyCon Analyser

Collect information from BillyCon pages and send it to a remote server for further analysis

// ==UserScript==
// @name           BvS BillyCon Analyser
// @namespace      TheSpy
// @description    Collect information from BillyCon pages and send it to a remote server for further analysis
// @version        1.12
// @history        1.12 New domain - animecubedgaming.com - Channel28
// @history        1.11 Now https compatible (Updated by Channel28)
// @history        1.10 Added grant permissions (Updated by Channel28)
// @history        1.09 Fixed: DOM issue (temporary fix)
// @history        1.08 Fixed: Free Time to Wander was not detected
// @history        1.07 Updated: Changed wander parsing from Regex to XPath
// @history        1.06 Updated: Quick fix to wanders with level up
// @history        1.05 Updated: Deals save modifiers now
// @history        1.04 Fixed: In some cases the script didn't parse information correctly; added protection and debug information
// @history        1.03 Added: Saving wander events
// @history        1.02 Added: Save day/hour along with the deal
// @history        1.02 Fixed: Owned cosplay sets don't send the (owned!) post-fix anymore
// @history        1.01 Fixed: Hot cosplay sets don't send the (Hot!) post-fix anymore
// @history        1.00 Initial release (credits: north made the cosplay part, I added the deal part)
// @include        http*://*animecubed.com/billy/bvs/billycon-character.html
// @include        http*://*animecubed.com/billy/bvs/billycon.html
// @include        http*://*animecubedgaming.com/billy/bvs/billycon-character.html
// @include        http*://*animecubedgaming.com/billy/bvs/billycon.html
// @grant          GM_addStyle
// @grant          GM_xmlhttpRequest
// ==/UserScript==

/***************************************************************
* DOM Storage Wrapper Class
* 
* Public members:
*     ctor({"session"|"local"}[, <namespace>])
*     setItem(<key>, <value>)
*     getItem(<key>, <default value>)
*     removeItem(<key>)
*     keys()
***************************************************************/
function Storage(type, namespace) {
	var object = this;

	if(typeof(type) != "string")
		type = "session";

	switch(type) {
		case "local": {
			object.storage = localStorage;
		} break;

		case "session": {
			object.storage = sessionStorage;
		} break;

		default: {
			object.storage = sessionStorage;
		} break;
	}

	if (!namespace || (typeof(namespace) != "string" && typeof(namespace) != "number"))
		namespace = "ScriptStorage";

	object.namespace = [namespace, "."].join("");

	object.setItem = function(key, value) {
		try {
			object.storage.setItem(escape([object.namespace, key].join("")), uneval(value));
		}
		catch(e) {
		}
	}

	object.getItem = function(key, defaultValue) {
		try {
			var value = object.storage.getItem(escape([object.namespace, key].join("")));
			if(value)
				return eval(value);
			else
				return defaultValue;
		}
		catch(e) {
			return defaultValue;
		}
	}

	object.removeItem = function(key) {
		try {
			object.storage.removeItem(escape([object.namespace, key].join("")));
		}
		catch(e) {
		}
	}

	object.keys = function() {
		var array = [];
		var i = 0;
		do {
			try {
				var key = unescape(object.storage.key(i++));
				if(key.indexOf(object.namespace) == 0 && object.storage.getItem(key))
					array.push(key.slice(object.namespace.length));
			}
			catch(e) {
				break;
			}
		} while(true);
		return array;
	}
}

// UI (credits: http://userscripts.org/users/dtkarlsson)
function Window(id, storage) {
	var my = this;
	my.id = id;
	my.offsetX = 0;
	my.offsetY = 0;
	my.moving = false;
	my.element = document.createElement("div");
	my.elementContainer = document.createElement("div");

	// Window dragging events
	my.drag = function(event) {
		if (my.moving) {
			my.elementContainer.style.left = (event.clientX - my.offsetX)+'px';
			my.elementContainer.style.top = (event.clientY - my.offsetY)+'px';
			event.preventDefault();
		}
	}
	my.stopDrag = function(event) {
		if (my.moving) {
			my.moving = false;
			var x = parseInt(my.elementContainer.style.left);
			var y = parseInt(my.elementContainer.style.top);
			if(x < 0) x = 0;
			if(y < 0) y = 0;
			storage.setItem(my.id + ".coord.x", x);
			storage.setItem(my.id + ".coord.y", y);
			my.elementContainer.style.opacity = 1;
			my.elementContainer.removeEventListener('mouseup', my.stopDrag, true);
			my.elementContainer.removeEventListener('mousemove', my.drag, true);
		}
	}
	my.startDrag = function(event) {
		if (event.button != 0) {
			my.moving = false;
			return;
		}
		my.offsetX = event.clientX - parseInt(my.elementContainer.style.left);
		my.offsetY = event.clientY - parseInt(my.elementContainer.style.top);
		my.moving = true;
		my.elementContainer.style.opacity = 0.75;
		event.preventDefault();
		my.elementContainer.addEventListener('mouseup', my.stopDrag, true);
		my.elementContainer.addEventListener('mousemove', my.drag, true);
	}

	my.elementContainer.id = id;
	my.elementContainer.className = "drag";
	document.body.appendChild(my.elementContainer);
	my.elementContainer.appendChild(my.element);
	my.elementContainer.addEventListener('mousedown', my.startDrag, true);

	if (storage.getItem(my.id + ".coord.x"))
		my.elementContainer.style.left = storage.getItem(my.id + ".coord.x") + "px";
	else
		my.elementContainer.style.left = "6px";
	if (storage.getItem(my.id + ".coord.y"))
		my.elementContainer.style.top = storage.getItem(my.id + ".coord.y") + "px";
	else
		my.elementContainer.style.top = "6px";
}

function FloatingAnalyser() {
	var my = this;
	my.window = new Window("BvSDataAnalyser", analyserSettings);
	GM_addStyle("#BvSDataAnalyser {border: 2px solid #00FF00; position: fixed; z-index: 100; font-size: 12px; font-family: courier new; color: #00FF00; background-color: black; padding: 6px; text-align: left; min-height: 16px; cursor: move;} #BvSDataAnalyser div div {border: 2px solid #00FF00; margin-top: 6px;}");

	my.addText = function(text) {
		if(my.window.element.innerHTML.length > 0)
			my.window.element.innerHTML = [my.window.element.innerHTML, text].join("<br/>");
		else
			my.window.element.innerHTML = text;
	}

	my.setText = function(text) {
		my.window.element.innerHTML = text;
	}
}

var analyserSettings = new Storage("local", "BvSBillyConAnalyser");
var analyserWindow;

// StripString(string)
function StripString(string) {
	string = string.replace(/^\s+/g, "");
	string = string.replace(/\s+$/g, "");
	string = string.replace(/\s+/g, " ");
	return string;
}

// PlayerName()
function PlayerName() {
	try {
		return document.evaluate("//input[@name='player' and @type='hidden']", document, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.value;
	}
	catch (e) {
		return "none";
	}
}

// BillyConTime()
function BillyConTime() {
	var o = new Object;
	o.day = "???";
	o.hour = "???";

	try {
		var snapTime = document.evaluate("//table[@class='constats']/tbody/tr[2]/td[position() > 5 and position() < 8]/b", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		if(snapTime.snapshotLength == 2 && (/\d+ AM/i.test(snapTime.snapshotItem(1).textContent) || /Noon/i.test(snapTime.snapshotItem(1).textContent) || /\d+ PM/i.test(snapTime.snapshotItem(1).textContent) || /Midnight/i.test(snapTime.snapshotItem(1).textContent) || /LATE/i.test(snapTime.snapshotItem(1).textContent))) {
			o.day = snapTime.snapshotItem(0).textContent;
			o.hour = snapTime.snapshotItem(1).textContent;
		}
	}
	catch(e) {
		o.day = "???";
		o.hour = "???";
	}

	return o;
}

// QueryServer(arguments)
function QueryServer(arguments, type) {
	try {
		arguments = [arguments, "version=1.07"].join("&");
		GM_xmlhttpRequest({
			method: "POST",
			url: ["http://bvs.ecansol.com/billyconanalyser/", type, "/"].join(""),
			headers: {
				"User-Agent": "Mozilla/5.0",
				"Accept": "text/xml",
				"Content-type": "application/x-www-form-urlencoded"
			},
			data: encodeURI(arguments),
			onerror: function(response) {
				analyserWindow.setText(["<b>Error:</b> ", response.status].join(""));
			},
			onload: function(response) {
				try {
					analyserWindow.setText(response.responseText);
				}
				catch(e) {
					alert(e);
				}
			}
		});
	}
	catch(e) {
		alert(e);
	}
}

// BillyConDealsAnalyser(snap)
function BillyConDealsAnalyser(snap) {
	try {
		var arguments = new Array();
		var billyConTime = BillyConTime();

		for(var i = 0; i < snap.snapshotLength; i++) {
			var match = snap.snapshotItem(i).textContent.replace(/\s+\(owned!\)/i, "").match(/^(.*) (\d+)M\n\(([^\)]+)\)/i);
			if(match && match[1] && match[2] && match[3]) {
				arguments.push(["deals[", i, "][name]=", encodeURIComponent(StripString(match[1]))].join(""));
				arguments.push(["deals[", i, "][monies]=", match[2]].join(""));
				arguments.push(["deals[", i, "][desc]=", encodeURIComponent(StripString(match[3]))].join(""));
				arguments.push(["deals[", i, "][day]=", StripString(billyConTime.day)].join(""));
				arguments.push(["deals[", i, "][hour]=", StripString(billyConTime.hour)].join(""));
			}
		}

		var snapDealModifiers = document.evaluate("//table[contains(translate(@bgcolor, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'),'111188')]/tbody/tr/td/font/i[contains(.,'Deal Modifiers')]/text()[last()]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		if(snapDealModifiers.snapshotLength == 1)
			arguments.push(["modifiers=", StripString(snapDealModifiers.snapshotItem(0).nodeValue)].join(""));

		arguments.push(["contributor=", PlayerName()].join(""));
		QueryServer(arguments.join("&"), "deals");
	}
	catch(e) {
		alert(e);
	}
}

// BillyConWanderAnalyser(snap)
function BillyConWanderAnalyser(snap) {
	try {
		var arguments = new Array();
		var billyConTime = BillyConTime();
		var event = "";
		var description = "";
		var modifiers = "";
		var ttw = 0;
		var extra = [];

		try {
			event = document.evaluate("./text()", snap.snapshotItem(0), null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.nodeValue;
		} catch(e) {}

		try {
			description = document.evaluate("./i/text()", snap.snapshotItem(0), null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.nodeValue;
		} catch(e) {}

		try {
			modifiers = document.evaluate("./font/b/text()", snap.snapshotItem(0), null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue.nodeValue;
		} catch(e) {}

		try {
			var snapExtra = document.evaluate("./font[2]//text()", snap.snapshotItem(0), null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
			for(var i = 0; i < snapExtra.snapshotLength; i++) {
				extra.push(snapExtra.snapshotItem(i).nodeValue);
			}
		} catch(e) {}

		try {
			var snapTTW = document.evaluate("./i/b[contains(.,'Time to Wander!')]", snap.snapshotItem(0), null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
			if(snapTTW.snapshotLength > 0)
				ttw = 1;
		} catch(e) {}

		if(event.length > 0) {
			if(!modifiers) modifiers = "";
			arguments.push(["event=", encodeURIComponent(StripString(event))].join(""));
			arguments.push(["description=", encodeURIComponent(StripString(description))].join(""));
			arguments.push(["modifiers=", encodeURIComponent(StripString(modifiers))].join(""));
			arguments.push(["ttw=", ttw].join(""));
			arguments.push(["extra=", encodeURIComponent(StripString(extra.join(" ")))].join(""));
			arguments.push(["day=", StripString(billyConTime.day)].join(""));
			arguments.push(["hour=", StripString(billyConTime.hour)].join(""));
		}

		arguments.push(["contributor=", PlayerName()].join(""));
		QueryServer(arguments.join("&"), "wander");
	}
	catch(e) {
		alert(e);
	}
}

// BillyConCosplayAnalyser()
function BillyConCosplayAnalyser() {
	try {
		var arguments = new Array();

		var name, stats;
		var snap = document.evaluate("//table[@width='80%']//table/tbody/tr[position()>1]", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
		for(var i = 0; i < snap.snapshotLength; i += 2) {
			name = snap.snapshotItem(i).textContent.replace(/\s+\(Hot!\)/i, "");
			stats = snap.snapshotItem(i+1).textContent.replace(/\?{3}/g, "0").match(/(\d+d\d+[+-]\d+|\d+d\d+|0)/g);
			if(stats.length == 4) {
				var type = ["head", "body", "prop", "combo"];
				for(var j = 0; j < 4; j++) {
					if(stats[j] != "0") {
						var match = stats[j].match(/(\d+)d(\d+)([+-]\d+)?/i);
						arguments.push(["cosplay[", StripString(name), "][", type[j], "][r]=", match[1]].join(""));
						arguments.push(["cosplay[", StripString(name), "][", type[j], "][d]=", match[2]].join(""));
						arguments.push(["cosplay[", StripString(name), "][", type[j], "][b]=", match[3]].join(""));
					}
				}
			}
		}

		arguments.push(["contributor=", PlayerName()].join(""));
		QueryServer(arguments.join("&"), "cosplay");
	}
	catch(e) {
		alert(e);
	}
}

if(/animecubed.com.billy.bvs.billycon.html/i.test(location.href)) {
	var snap = document.evaluate("//form[@name='condroom']/table/tbody/tr/td/label", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	if(snap.snapshotLength > 0) {
		analyserWindow = new FloatingAnalyser();
		analyserWindow.setText("<blink>Loading...</blink>");
		var billyConDealsAnalyser = new BillyConDealsAnalyser(snap);
	}
	var snap = document.evaluate("//table/tbody/tr/td/b[contains(.,'Wandering Encounter')]/..", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
	if(snap.snapshotLength > 0) {
		analyserWindow = new FloatingAnalyser();
		analyserWindow.setText("<blink>Loading...</blink>");
		var billyConWanderAnalyser = new BillyConWanderAnalyser(snap);
	}
}

if(/animecubed.com.billy.bvs.billycon.character.html/i.test(location.href)) {
	analyserWindow = new FloatingAnalyser();
	analyserWindow.setText("<blink>Loading...</blink>");
	var billyConCosplayAnalyser = new BillyConCosplayAnalyser();
}