Mount Olympus

Common features shared amongst all Olympian scripts.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Mount Olympus
// @namespace    mobiusevalon.tibbius.com
// @version      1.5
// @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,
	to_avg_type:"confidence"
};

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 .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%;}"+
		"#olympian_settings .container .description .toggle {margin-right: 5px;}"+
		"#olympian_settings .container .subtitle.to_attr {width: 50px; display: inline-block; margin-right: 5px; text-align: right;}"+
		"#olympian_settings .container .description .collapsed {display: inline-block; width: 350px; height: 16px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;}"+
		"#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;}"+
		"#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("__settings")) 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.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.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."
		}),
		olympus.settings.generate({
			option:"to_avg_type",
			type:"dropdown",
			selections:["simple","weighted","confidence"],
			value:olympus.settings.get(olympus,"to_avg_type"),
			name:"TO average type",
			desc:
				"The formula that is used to determine a requester's average Turkopticon rating.<br><br>"+
				"A simple average is what you learned in grade school.  Each attribute is added up and then simply "+
				"divided by the number of attributes.<br><br>"+
				"A weighted average makes certain attributes more important than others.  When an attribute has a higher "+
				"weight, the requester's overall average will change proportionately because you're stressing or minimizing "+
				"certain attributes over tohers.  The weights can be changed using the next option.<br><br>"+
				"A confidence average will calculate the simple average and then put it through a Bayesian function.  The "+
				"short explanation of what that means is that high averages and requesters with few reviews will have their "+
				"scores dragged downward due to a lack of \"confidence\" in the precision of the result."
		}),
		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:
						"When using the weighted TO average, the weights provided here change the importance of each value and affects "+
						"the requester's overall average.  Most workers prioritize pay and fairness over communication and speed, and "+
						"generally use values like 6, 4, 1, 1 respectively.  Decimals are valid, as well as zero which removes that "+
						"dimension from the calculation."
				})
			)
	];
};

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",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);
	},
	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 = (olympian.__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) {
		if($.type(olympian) === "object" && olympian.hasOwnProperty("__name")) {
			var settings = olympus.utilities.localstorage_obj(olympian.__name+"_settings"),
				defaults = olympian.default_settings;
			if($.type(settings) === "object") {
				$.each(defaults,function(k,v) {
					if(!settings.hasOwnProperty(k)) settings[k] = v;
				});
			}
			else settings = defaults;
			olympian.__settings = settings;
		}
	},
	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 = (olympian.__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);
			olympian.__settings = 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];
		}
	},
	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();
});