Mount Olympus

Common features shared amongst all Olympian scripts.

当前为 2016-09-19 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Mount Olympus
// @namespace    mobiusevalon.tibbius.com
// @version      1.6
// @author       Mobius Evalon
// @description  Common features shared amongst all Olympian scripts.
// @license      Creative Commons Attribution-ShareAlike 4.0; http://creativecommons.org/licenses/by-sa/4.0/
// @require      https://code.jquery.com/jquery-1.12.4.min.js
// @require      https://code.jquery.com/ui/1.11.4/jquery-ui.min.js
// @include      /^https{0,1}:\/\/\w{0,}\.?mturk\.com.+/
// @include      /^https{0,1}:\/\/\w*\.amazon\.com\/ap\/signin.*(?:openid\.assoc_handle|pf_rd_i)=amzn_mturk/
// @exclude      /&hit_scraper$/
// @exclude      /\/HM$/
// @grant        none
// ==/UserScript==

if(window.olympus === undefined) window.olympus = {};

// there is a reason they are initialized in this order
window.olympus.__name = "olympus";
window.olympus.known_olympians = ["harpocrates","hermes","artemis","athena"];
window.olympus.default_settings = {
	query_turkopticon:true,
	use_to_cache:true,
	to_cache_timeout:10800000,
	to_pay_weight: 6.5,
	to_fair_weight:4,
	to_fast_weight:1,
	to_comm_weight:0.5,
	bayesian_to:true
};

window.olympus.__init = function() {
	console.log("olympus init");
	this.settings.init(this);
	olympus.style.add(
		"#javascriptDependentFunctionality {display: block !important;}"+
		".dialog.floats {border-radius: 8px; border: 2px solid #000000; max-height: 550px; position: absolute !important; z-index: 500; background-color: #7fb4cf; top: 25px; left: 200px; font-size: 12px;} "+
		".dialog.narrow {width: 300px; min-width: 300px;} "+
		".dialog.wide {width: 550px; min-width: 550px;} "+
		".dialog .scrolling-content {max-height: 350px; overflow-y: auto;} "+
		".dialog .actions {margin: 10px auto; padding: 0px; text-align: center; display: block;} "+
		".dialog .actions input:not(:last-of-type) {margin-right: 15px;} "+
		".dialog .head {padding: 0px; margin: 10px auto; font-size: 175%; font-weight: bold; width: 100%; text-align: center; cursor: move;} "+
		"#olympian_help p.inset {margin-left: 25px;}"+
		"#olympian_help p.inset b {margin-left: -25px; display: block;}"+
		"#olympian_settings .sidebar {float: left; min-width: 100px; padding-left: 5px;}"+
		"#olympian_settings .sidebar .tab {height: 30px; line-height: 30px; text-align: center; font-weight: bold; cursor: pointer;}"+
		"#olympian_settings .sidebar .tab.active {background-color: #88c1de;}"+
		"#olympian_settings .container {background-color: #88c1de; padding: 5px; margin-right: 5px; min-height: 150px;}"+
		"#olympian_settings .subheader {font-size: 125%; font-weight: bold; margin-bottom: 10px; background-color: #96d5f5; padding: 5px 0px 5px 5px;}"+
		"#olympian_settings .container .option_container {display: block;}"+
		"#olympian_settings .container .option_container b.name {width: 150px; display: inline-block; margin-right: 10px;}"+
		"#olympian_settings .container .description {margin-top: 5px; margin-bottom: 10px; font-size: 90%; padding-left: 10px;}"+
		"#olympian_settings .container .description .toggle {margin-right: 5px;}"+
		"#olympian_settings .container .description .collapsed {display: inline-block; width: 350px; height: 16px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;}"+
		"#olympian_settings .container .subtitle.to_attr {width: 50px; display: inline-block; margin-right: 5px; text-align: right;}"+
		"#olympian_settings .container .fa-toggle-off, #olympian_settings .container .fa-toggle-on, #olympian_settings .container .fa-plus-square-o, #olympian_settings .container .fa-minus-square-o {cursor: pointer;}"+
		"#olympian_settings .container input[type='number'] {width: 45px;}"+
		"#olympian_settings .container .plain {margin-bottom: 20px;}"+
		"#open_olympus_settings {cursor: pointer;}"+
		".anim_pulse {animation-name: anim_pulse; animation-duration: 350ms; animation-iteration-count: infinite; animation-timing-function: linear; animation-direction: alternate;}"+
		"@keyframes anim_pulse {from {opacity: 1;} to {opacity: 0.25;}} "
	);

	// append the fontawesome stylesheet to the page if it does not exist
	if(!$("link[rel='stylesheet'][href$='font-awesome.min.css']").length) $("head").append(
		$("<link/>")
			.attr({
				"data-pantheon":"olympus",
				"rel":"stylesheet",
				"href":"https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"
			})
	);

	Array.prototype.contains = function(item) {
		return (this.indexOf(item) > -1);
	};

	String.prototype.collapseWhitespace = function() {
		return this.replace(/\s{2,}/g," ").trim();
	};

	String.prototype.ucFirst = function() {
		return (this.charAt(0).toUpperCase()+this.slice(1));
	};

	String.prototype.contains = function(substring) {
		return (this.indexOf(substring) > -1);
	};

	// append the help window to the document
	$("body").append(
		$("<div/>")
			.attr({
				"data-pantheon":"olympus",
				"id":"olympian_help",
				"class":"dialog wide floats"
			})
			.append(
				$("<h1/>")
					.attr("class","head")
					.text("Olympian help"),
				$("<div/>")
					.attr("class","scrolling-content")
					.append(
						$("<div/>").attr("class","explain")
					),
				$("<div/>")
					.attr("class","actions")
					.append(
						$("<button/>")
							.text("Close")
							.click(function() {
								olympus.help.hide();
							})
					)
			)
			.hide(),
		$("<div/>")
			.attr({
				"data-pantheon":"olympus",
				"id":"olympian_settings",
				"class":"dialog wide floats"
			})
			.append(
				$("<h1/>")
					.attr("class","head")
					.text("Olympian settings"),
				$("<div/>").attr("class","sidebar"),
				$("<div/>")
					.attr("class","scrolling-content")
					.append(
						$("<div/>").attr("class","container")
					),
				$("<div/>")
					.attr("class","actions")
					.append(
						$("<button/>")
							.text("Close")
							.click(function() {
								olympus.settings.hide();
							})
					)
			)
			.hide()
	);

	// use jqueryui.draggable() to make the help window movable
	$(".floats").draggable({handle:"h1.head"});

	// put the settings icon on the page
	$("span.header_links").first().before(
		olympus.settings.button("olympus")
			.addClass("fa-3x")
			.css({
				"float":"right",
				"margin-left":"5px"
			})
	);

	$.each(this.known_olympians,function(index,olympian) {
		if($.type(olympus[olympian]) === "object") { // olympian is installed
			if(olympus[olympian].hasOwnProperty("__configurable")) olympus.settings.init(olympus[olympian]);
			olympus[olympian].__init();
		}
	});
};

window.olympus.__configurable = function() {
	function _gen_to_weight_element(type) {
		return $("<span/>")
			.attr("class","subtitle to_attr")
			.text(type.ucFirst()+":")
			.add(
				olympus.settings._gen_option({
					option:("to_"+type+"_weight"),
					type:"number",
					value:olympus.settings.get(olympus,("to_"+type+"_weight"))
				})
			);
	}

	return [
		olympus.settings.explain({
			type:"plain",
			desc:
				"Olympus settings affect all Olympian scripts in all tabs.  For instance, if you disable Turkopticon queries "+
				"then every Olympian script will stop querying Turkopticon immediately."
		}),
		olympus.settings.generate({
			option:"query_turkopticon",
			type:"checkbox",
			value:olympus.settings.get(olympus,"query_turkopticon"),
			name:"Query Turkopticon",
			desc:
				"Sometimes, the Turkopticon server goes AWOL and any scripts that request data from it (such as this one) "+
				"will hang for several minutes in the absence of a response.  When this happens, it's best to turn off "+
				"Turkopticon queries for a while.  When Turkopticon queries are disabled, Olympus will continue to use "+
				"cached information as allowed by the following options."
		}),
		olympus.settings.generate({
			option:"use_to_cache",
			type:"checkbox",
			value:olympus.settings.get(olympus,"use_to_cache"),
			name:"Cache TO data",
			desc:
				"After completing a Turkopticon query, Olympus can save that data to your computer to allow for rapid retrieval "+
				"later to speed up the Olympian scripts and prevent a lot of unnecessary queries.  Be aware that this option does "+
				"not control whether or not Olympus uses existing cached data, but instead whether or not Olympus stores new data. "+
				"Disabling this option will not delete the Turkopticon data that Olympus has already cached."
		}),
		olympus.settings.generate({
			option:"to_cache_timeout",
			type:"number",
			value:(olympus.settings.get(olympus,"to_cache_timeout")/3600000),
			name:"TO cache life",
			desc:
				"The number of hours that Olympus will consider cached data recent enough to use that instead of querying "+
				"Turkopticon for it.  Decimals are valid (e.g. 1.5 for 90 minutes) and has a minimum value of 0.5 "+
				"(30 minutes).  If you want to disable caching, use the option above."
		}),
		olympus.settings
			._gen_option_wrapper({
				name:"Turkopticon weights",
				elements:4
			})
			.append(
				_gen_to_weight_element("pay"),
				_gen_to_weight_element("fast"),
				$("<br/>"),
				_gen_to_weight_element("fair").first().css("margin-left","160px").end(),
				_gen_to_weight_element("comm")
			)
			.add(
				olympus.settings._gen_desc({
					desc:
						"The weight of a Turkopticon attribute will proportionately increase or decrease the requester's overall average "+
						"based on the multipliers provided here.  A flat statistical average can sometimes make a requester appear worse "+
						"on the surface because of a low score, like a requester with a 5 for pay, 5 for fair, and 1 for comm and fast.  A "+
						"flat average gives this requester a 3 overall TO average, while you could e.g. weight pay 5x, fair 4x, and 0.5x for "+
						"comm and fast to give them a weighted average of 4.6.  It's basically a way for you to alter the attributes to "+
						"your preference.  Most workers use 6, 4, 1, 1 for pay, fast, fair, and comm respectively, and setting these to all "+
						"1 will give you the flat statistical average."
				})
			),
		olympus.settings.generate({
			option:"bayesian_to",
			type:"checkbox",
			value:olympus.settings.get(olympus,"bayesian_to"),
			name:"TO confidence average",
			desc:
				"Whether or not to stuff the calculated TO average (after it has been weighted from the above settings) into a Bayesian "+
				"function.  The layman explanation of what a Bayesian function does is lowering the given average based on a lack of faith "+
				"in the accuracy of the result, which is the most obvious when a requester has few reviews or a very high score.  For instance, "+
				"a requester with a single glowing review and all 5 ratings will have a confidence average of about 2.85 because we don't have "+
				"enough information to corroborate this one person's opinions."
		}),
		olympus.settings.generate({
			option:"clear_to_cache",
			type:"button",
			action:olympus.utilities.clear_to_cache,
			name:"Clear TO cache",
			desc:
				"The Turkopticon cache is using about <span data-function='to_cache_size'></span>, or approximately "+
				"<span data-function='to_cache_usage'></span> of the available storage for the mturk.com domain."
		})
	];
};

window.olympus.__parse_settings = function(settings) {
	settings.to_cache_timeout = Math.max(settings.to_cache_timeout*3600000,1800000);
	$.each(["pay","fair","fast","comm"],function(index,value) {
		if(settings["to_"+value+"_weight"] < 0) settings["to_"+value+"_weight"] = 0;
	});
	olympus.settings.update(olympus,settings);
};

window.olympus.help = {
	__topics:{},
	add:function(obj) {
		if($.type(obj) === "object" && Object.keys(obj).length) {
			$.each(obj,function(key,val) {
				olympus.help.__topics[key] = val;
			});
		}
	},
	display:function(topic) {
		if(this.has_topic(topic)) {
			$("#olympian_help .explain").html(this.__topics[topic]);
			// parse elements with special functions
			olympus.utilities.parse_deferred_functions($("#olympian_help .explain"));
			// show help window
			$("#olympian_help").show();
		}
	},
	has_topic:function(topic) {
		return this.__topics.hasOwnProperty(topic);
	},
	hide:function() {
		$("#olympian_help").hide();
	}
};

window.olympus.settings = {
	_gen_desc:function(config) {
		return $("<div/>")
			.attr("class","description")
			.append(
				$("<span/>")
					.attr("class","collapsed")
					.html(config.desc)
					.prepend(
						$("<span/>")
							.attr("class","toggle fa fa-lg fa-plus-square-o")
							.click(function() {
								$(this).toggleClass("fa-plus-square-o fa-minus-square-o");
								if($(this).hasClass("fa-plus-square-o")) $(this).parent().addClass("collapsed");
								else $(this).parent().removeClass("collapsed");
							})
					)
			);
	},
	_gen_option:function(config) {
		switch(config.type) {
			case "checkbox": return $("<span/>")
				.attr({
					"class":("fa fa-lg fa-toggle-"+((config.value === true) ? "on" : "off")),
					"id":config.option
				})
				.click(function() {
					$(this).toggleClass("fa-toggle-off fa-toggle-on");
				});
			case "number": return $("<input/>")
				.attr({
					"type":"number",
					"id":config.option
				})
				.val(config.value ? config.value : "");
			case "button": return $("<button/>")
				.attr("id",config.option)
				.text(config.name)
				.click(function() {
					config.action();
				});
			case "dropdown": {
				var options = [];
				$.each(config.selections,function(index,value) {
					options.push(
						$("<option/>")
							.attr("value",olympus.utilities.html_friendly(value))
							.text(value.ucFirst())
					);
				});
				return $("<select/>")
					.attr("id",config.option)
					.append(options)
					.val(config.value);
			}
		}
	},
	_gen_option_wrapper:function(config) {
		return $((config.hasOwnProperty("elements") && config.elements > 1) ? "<div/>" : "<label/>")
			.attr("class","option_container")
			.append(
				$("<b/>")
					.attr("class","name")
					.text(config.name)
			);
	},
	button:function(source) {
		return $("<span/>")
			.attr({
				"class":"fa fa-cogs",
				"id":"open_olympus_settings",
				"title":"Open Olympus settings"
			})
			.click(function() {
				olympus.settings.open(source);
			});
	},
	change_tab:function(tab) {
		if($("#olympian_settings .sidebar .tab.active").length) this.commit_page();
		$("#olympian_settings .sidebar .tab").removeClass("active");
		$("#"+tab+"_tab").addClass("active");
		$("#olympian_settings .container").empty().append(
			(tab === "olympus" ? olympus.__configurable() : olympus[tab].__configurable)
		);
		olympus.utilities.parse_deferred_functions($("#olympian_settings .container"));
	},
	commit_page:function() {
		var olympian = $("#olympian_settings .sidebar .tab.active").attr("id").slice(0,-4),
			settings = {};
		$.each($("#olympian_settings .container *[id]"),function(index,$element) {
			$element = $($element);
			switch($element.prop("tagName")) {
				case "INPUT": case "SELECT": {
					settings[$element.attr("id")] = $element.val();
					break;
				}
				case "SPAN": {
					if($element.hasClass("fa-toggle-on") || $element.hasClass("fa-toggle-off")) settings[$element.attr("id")] = $element.hasClass("fa-toggle-on");
					break;
				}
			}
		});
		if(olympian === "olympus") olympus.__parse_settings(settings);
		else olympus[olympian].__parse_settings(settings);
	},
	explain:function(config) {
		return $("<div/>")
			.attr("class",config.type)
			.html(config.desc);
	},
	generate:function(config) {
		return this._gen_option_wrapper(config)
			.append(this._gen_option(config))
			.add(this._gen_desc(config));
	},
	get:function() {
		if($.type(arguments[0]) === "object" && arguments[0].hasOwnProperty("__name")) {
			var olympian = arguments[0],
				settings = (olympus.utilities.localstorage_obj(olympian.__name+"_settings") || olympian.default_settings);
			if(arguments.length < 2) return settings;
			else if($.type(arguments[1]) === "string") return settings[arguments[1]];
		}
	},
	hide:function() {
		this.commit_page();
		$("#olympian_settings")
			.find(".sidebar .tab").removeClass("active")
			.end().hide();
	},
	init:function(olympian) {
		// this function basically only exists to make sure that new options are added into
		// each olympian's settings when the script is updated
		if($.type(olympian) === "object" && olympian.hasOwnProperty("__name")) {
			var settings = olympus.utilities.localstorage_obj(olympian.__name+"_settings"),
				defaults = olympian.default_settings;
			if($.type(settings) === "object") {
				var original_len = Object.keys(settings).length;
				$.each(defaults,function(k,v) {
					if(!settings.hasOwnProperty(k)) settings[k] = v;
				});
				if(Object.keys(settings).length !== original_len) localStorage[olympian.__name+"_settings"] = JSON.stringify(settings); // new options exist
			}
		}
	},
	open:function(source) {
		function __create_tab(tab) {
			return $("<div/>")
				.attr({
					"class":"tab",
					"id":(tab+"_tab")
				})
				.text(tab.ucFirst())
				.click(function() {
					olympus.settings.change_tab($(this).attr("id").slice(0,-4));
				});
		}

		if(!$("#olympian_settings .sidebar .tab").length) {
			$("#olympian_settings .sidebar").append(
				__create_tab("olympus")
			);
			$.each(olympus.known_olympians,function(index,olympian) {
				if($.type(olympus[olympian]) === "object" && olympus[olympian].hasOwnProperty("__configurable")) $("#olympian_settings .sidebar").append(
					__create_tab(olympian)
				);
			});
		}
		this.change_tab(source);
		$("#olympian_settings").show();
	},
	update:function() {
		if(arguments.length > 1 && $.type(arguments[0]) === "object" && arguments[0].hasOwnProperty("__name")) {
			var olympian = arguments[0],
				settings = (olympus.utilities.localstorage_obj(olympian.__name+"_settings") || olympian.default_settings);
			if($.type(arguments[1]) === "object") {
				$.each(arguments[1],function(key,val) {
					if(settings.hasOwnProperty(key)) settings[key] = val;
				});
			}
			else if($.type(arguments[1]) === "string" && arguments.length > 2) if(settings.hasOwnProperty(arguments[1])) settings[arguments[1]] = arguments[2];

			localStorage[olympian.__name+"_settings"] = JSON.stringify(settings);
		}
	}
};

window.olympus.style = {
	__css:"",
	__commit:function() {
		// retrieve the olympian style node, or create it if it does not yet exist
		var $style_node = $("#olympian_css");
		if(!$style_node.length) {
			$style_node = $("<style/>")
				.attr({
					"data-pantheon":"olympus",
					"id":"olympian_css",
					"type":"text/css"
				});
			$("head").append($style_node);
		}

		// update the olympian style node with the new css
		$style_node.text(this.__css);
	},
	add:function(new_css,tokens) {
		if($.type(tokens) === "object" && Object.keys(tokens).length) new_css = this.expand(new_css,tokens);
		this.__css += new_css;
		this.__commit();
	},
	expand:function(css,tokens) {
		// olympians sometimes use bracketed tokens in their css to allow for centralized
		// style definitions from functions or for swapping values easily
		$.each(tokens,function(key,val) {css = css.replace(new RegExp(("\\["+key+"\\]"),"gi"),val);});
		return css;
	}
};

window.olympus.utilities = {
	datetime:{
		__day_string:function(int) {
			switch(int) {
				case 0: return "Sunday";
				case 1: return "Monday";
				case 2: return "Tuesday";
				case 3: return "Wednesday";
				case 4: return "Thursday";
				case 5: return "Friday";
				case 6: return "Saturday";
			}
		},
		__meridiem:function(int) {
			if(int > 12) return "pm";
			else return "am";
		},
		__meridiem_hour:function(int) {
			if(int > 12) int -= 12;
			return int;
		},
		__month_string:function(int) {
			switch(int) {
				case 0: return "January";
				case 1: return "February";
				case 2: return "March";
				case 3: return "April";
				case 4: return "May";
				case 5: return "June";
				case 6: return "July";
				case 7: return "August";
				case 8: return "September";
				case 9: return "October";
				case 10: return "November";
				case 11: return "December";
			}
		},
		__ordinal:function(int) {
			switch(int) {
				case 1: case 21: case 31: return "st";
				case 2: case 22: return "nd";
				case 3: case 23: return "rd";
			}
			return "th";
		},
		__short_year:function(int) {
			return (""+int).slice(-2);
		},
		getDayString:function() {
			return this.__day_string(this.__date.getDay());
		},
		getMeridiem:function() {
			return this.__meridiem(this.__date.getHours());
		},
		getMeridiemHours:function() {
			return this.__meridiem_hour(this.__date.getHours());
		},
		getMonthString:function() {
			return this.__month_string(this.__date.getMonth());
		},
		getOrdinal:function() {
			return this.__ordinal(this.__date.getDate());
		},
		getShortYear:function() {
			return this.__short_year(this.__date.getFullYear());
		},
		getUTCDayString:function() {
			return this.__day_string(this.__date.getUTCDay());
		},
		getUTCMeridiem:function() {
			return this.__meridiem(this.__date.getUTCHours());
		},
		getUTCMeridiemHours:function() {
			return this.__meridiem_hour(this.__date.getUTCHours());
		},
		getUTCMonthString:function() {
			return this.__month_string(this.__date.getUTCMonth());
		},
		getUTCOrdinal:function() {
			return this.__ordinal(this.__date.getUTCDate());
		},
		getUTCShortYear:function() {
			return this.__short_year(this.__date.getUTCFullYear());
		},
		getTokenizedOutput:function(t) {
			var r = "",
				i = -1;
			while(i++ < t.length) {
				switch(t.charAt(i)) {
					// escape sequence, ignore following character by advancing index beyond it
					case '\\': {r += t.charAt(++i); break;}

					// local year
					case 'y': {r += this.getShortYear(); break;}
					case 'Y': {r += this.__date.getFullYear(); break;}
					// local month
					case 'n': {r += (this.__date.getMonth()+1); break;}
					case 'm': {r += olympus.utilities.pad_string(this.__date.getMonth()+1,2); break;}
					case 'F': {r += this.getMonthString(); break;}
					case 'M': {r += this.getMonthString().slice(0,3); break;}
					// local day
					case 'j': {r += this.__date.getDate(); break;}
					case 'd': {r += olympus.utilities.pad_string(this.__date.getDate(),2); break;}
					case 'l': {r += this.getDayString(); break;}
					case 'D': {r += this.getDayString().slice(0,3); break;}
					case 'S': {r += this.getOrdinal(); break;}
					// local hour
					case 'g': {r += this.getMeridiemHours(); break;}
					case 'h': {r += olympus.utilities.pad_string(this.getMeridiemHours(),2); break;}
					case 'G': {r += this.__date.getHours(); break;}
					case 'H': {r += olympus.utilities.pad_string(this.__date.getHours(),2); break;}
					case 'a': {r += this.getMeridiem(); break;}
					case 'A': {r += this.getMeridiem().toUpperCase(); break;}
					// local minute, second
					case 'i': {r += olympus.utilities.pad_string(this.__date.getMinutes(),2); break;}
					case 's': {r += olympus.utilities.pad_string(this.__date.getSeconds(),2); break;}

					// utc year
					case 'z': {r += this.getUTCShortYear(); break;}
					case 'Z': {r += this.__date.getUTCFullYear(); break;}
					// utc month
					case 'p': {r += (this.__date.getUTCMonth()+1); break;}
					case 'q': {r += olympus.utilities.pad_string(this.__date.getUTCMonth()+1,2); break;}
					case 'T': {r += this.getUTCMonthString(); break;}
					case 'U': {r += this.getUTCMonthString().slice(0,3); break;}
					// utc day
					case 'f': {r += this.__date.getUTCDate(); break;}
					case 'e': {r += olympus.utilities.pad_string(this.__date.getUTCDate(),2); break;}
					case 'k': {r += this.getUTCDayString(); break;}
					case 'E': {r += this.getUTCDayString().slice(0,3); break;}
					case 'R': {r += this.getUTCOrdinal(); break;}
					// utc hour
					case 'b': {r += this.getUTCMeridiemHours(); break;}
					case 'c': {r += olympus.utilities.pad_string(this.getUTCMeridiemHours(),2); break;}
					case 'B': {r += this.__date.getUTCHours(); break;}
					case 'C': {r += olympus.utilities.pad_string(this.__date.getUTCHours()); break;}
					case 'o': {r += this.getUTCMeridiem(); break;}
					case 'O': {r += this.getUTCMeridiem().toUpperCase() ;break;}
					// utc minute, second
					case 'w': {r += olympus.utilities.pad_string(this.__date.getUTCMinutes(),2); break;}
					case 'x': {r += olympus.utilities.pad_string(this.__date.getUTCSeconds(),2); break;}

					default: {r += t.charAt(i); break;}
				}
			}
			return r;
		},
		output:function() {
			if(arguments.length) {
				if(arguments.length > 1) this.__date = new Date(arguments[0]);
				if($.type(this.__date) !== "undefined") return this.getTokenizedOutput(arguments[arguments.length-1]);
			}
		}
	},
	ajax_get:function(mirrors,params,callback,scope) {
		var result = "";

		function exit() {
			if($.type(callback) === "function") callback.call(scope,result);
		}

		function domain_name(s) {
			return s.match(/^https{0,1}:\/\/(.+?)\//i)[1];
		}

		function request(url) {
			$.ajax({
				async:true,
				method:"GET",
				url:(url+params)
			})
			.fail(function() {
				console.log("Mount Olympus get request: attempt to gather data from '"+domain_name(url)+"' mirror failed");
				var idx = (mirrors.indexOf(url)+1);
				if(idx < mirrors.length) {
					console.log("Mount Olympus get request: attempting data request from mirror '"+domain_name(mirrors[idx])+"'...");
					request(mirrors[idx]);
				}
				else {
					console.log("Mount Olympus get request: attempts to gather data from all available mirrors has failed");
					exit();
				}
			})
			.done(function(response) {
				if(response.length) {
					console.log("Mount Olympus get request: query was successful");
					result = response;
				}
				exit();
			});
		}

		request(mirrors[0]);
	},
	bkmg:function(bytes) {
		var multiple = 0;
		while(bytes > 1024) {
			multiple++;
			bytes /= 1024;
		}
		return (""+bytes.toFixed(2)+" "+["","kilo","mega","giga"][multiple]+"byte"+olympus.utilities.plural(bytes));
	},
	clear_page:function(title) {
		// when an olympian wants an independent full-page display.  every element added to
		// the top level of the document has a data-pantheon attribute for exactly this purpose:
		// if an element does not have that attribute, it is removed
		$("head")
			.children().not("[data-pantheon]").remove()
			.end().end().append(
				$("<title/>").text(title)
			);
		$("body")
			.removeAttr("onload onLoad")
			.children().not("[data-pantheon]").remove();
	},
	clear_to_cache:function() {
		if(confirm("Are you sure you want to delete the Turkopticon cache?")) localStorage.removeItem("olympian_to_cache");
	},
	dhms:function(secs) {
		// takes a number of seconds (chiefly, hitAutoAppDelayInSeconds) and returns a
		// "friendly" value in seconds, minutes, hours, or days.  has a precision of
		// tenths, e.g. "1.5 days" or "6.7 hours"
		function output(multiple,name) {
			function zeroes(num) {
				// removes ugly trailing zeroes (e.g. "1.0 days" or "2.40 hours")
				return +num.toFixed(1);
			}
			var units = zeroes((secs/multiple));
			return (""+units+" "+name+olympus.utilities.plural(units));
		}

		if($.type(secs) !== "number") secs = Math.round(secs*1);

		if(secs < 60) return output(1,"second");
		else if(secs < 3600) return output(60,"minute");
		else if(secs < 86400) return output(3600,"hour");
		else return output(86400,"day");
	},
	href_group_id:function(href) {
		if($.type(href) === "string") {
			href = href.match(/groupId=([^&\s]+)/i);
			if($.type(href) === "array") return href[1];
		}
	},
	html_friendly:function(string) {
		return string.collapseWhitespace().replace(/\s/g,"_");
	},
	href_requester_id:function(href) {
		if($.type(href) === "string") {
			href = href.match(/requesterId=([^&\s]+)/i);
			if($.type(href) === "array") return href[1];
		}
	},
	json_obj:function(json) {
		var obj;
		if(typeof json === "string" && json.trim().length) {
			try {obj = JSON.parse(json);}
			catch(e) {console.log("Malformed JSON object.  Error message from JSON library: ["+e.message+"]");}
		}
		return obj;
	},
	localstorage_obj:function(key) {
		var obj = this.json_obj(localStorage.getItem(key));
		if(typeof obj !== "object") localStorage.removeItem(key);
		return obj;
	},
	pad_string:function(string,width,padding,side) {
		string = (""+string);
		var pad_item = (padding || "0"),
			half = ((width-string.length)/2);
		padding = "";
		while((string.length+padding.length) < width) padding = (padding+pad_item);
		if(side === "both") return (padding.slice(0,Math.floor(half))+string+padding.slice(Math.ceil(half)*-1));
		else if(side === "right") return (string+padding).slice(0,width);
		else return (padding+string).slice(width*-1);
	},
	parse_deferred_functions:function($context) {
		$context.find("*[data-function]").each(function() {
			switch($(this).attr("data-function")) {
				case "desc2fa": {
					$(this)
						.addClass("fa fa-fw fa-2x "+olympus.athena.desc2fa($(this).attr("data-args")))
						.removeAttr("data-function data-args");
					break;
				}
				case "to_cache_size": {
					$(this).replaceWith(
						document.createTextNode(""+olympus.utilities.bkmg(localStorage.olympian_to_cache.length))
					);
					break;
				}
				case "to_cache_usage": {
					$(this).replaceWith(
						document.createTextNode(""+(localStorage.olympian_to_cache.length/10485760).toFixed(2)+"%")
					);
					break;
				}
			}
		});
	},
	plural:function(num) {
		// returns the letter s if the number is not 1.  just for pretty display
		// to say something like "2 widgets" instead of "2 widget"
		if($.type(num) !== "number") num = +num;
		if(num != 1) return "s";
		return "";
	},
	turkopticon:function(rids,callback,scope) {
		var to_mirrors = [
				"https://mturk-api.istrack.in/multi-attrs.php?ids=",
				"https://turkopticon.ucsd.edu/api/multi-attrs.php?ids="
			],
			query_rids = [],
			query_result = {};

		function exit() {
			if($.type(callback) === "function") callback.call(scope,query_result);
		}

		// to caching functions of this script retain turkopticon information for 4 hours
		// and reuse it as necessary instead of querying the server every time
		function set_cache(rid,attrs) {
			if(olympus.settings.get(olympus,"use_to_cache")) {
				var to_cache = (olympus.utilities.localstorage_obj("olympian_to_cache") || {});
				if($.type(attrs) !== "object") attrs = {};
				attrs.cache_time = new Date().getTime();
				to_cache[rid] = attrs;
				localStorage.olympian_to_cache = JSON.stringify(to_cache);
			}
		}

		function get_cache(rid) {
			var to_cache = olympus.utilities.localstorage_obj("olympian_to_cache");
			if($.type(to_cache) === "object" && to_cache.hasOwnProperty(rid)) {
				var attrs = to_cache[rid];
				if((new Date().getTime()) - (attrs.cache_time*1) < olympus.settings.get(olympus,"to_cache_timeout")) return attrs;
			}
		}

		// check the cache for relevant data we can use and query for the rest
		$.each(rids,function(k,v) {
			var cached = get_cache(v);
			if($.type(cached) === "object") query_result[v] = cached;
			else query_rids.push(v);
		});
		var num_cached = Object.keys(query_result).length,
			num_queried = query_rids.length;
		console.log("Mount Olympus Turkopticon: "+(num_cached > 0 ? ("using cached data for "+num_cached+" requesters") : "no available or timely cached data")+"; "+(num_queried > 0 ? ("query required for "+num_queried+" requesters") : "no queries necessary"));
		if(olympus.settings.get(olympus,"query_turkopticon") && query_rids.length) {
			this.ajax_get(to_mirrors,query_rids.join(","),function(response) {
				var jsobj = olympus.utilities.json_obj(response);
				if($.type(jsobj) === "object") {
					$.each(jsobj,function(rid,attrs) {
						set_cache(rid,attrs);
						query_result[rid] = attrs;
					});
				}
				else console.log("Mount Olympus Turkopticon: query was successful but the response was malformed");
				exit();
			});
		}
		else exit();
	}
};

$(document).ready(function() {
	olympus.__init();
});