BvS BillyCon Analyser

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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();
}