Mount Olympus

Common features shared amongst all Olympian scripts.

目前為 2016-09-10 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Mount Olympus
// @namespace    mobiusevalon.tibbius.com
// @version      1.0
// @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.*$/
// @exclude      /&hit_scraper$/
// @grant        none
// ==/UserScript==

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

// all of these are added separately because then i don't overwrite the entire olympus object,
// making it possible to initialize the scripts out of order
window.olympus.__init = function() {
	console.log("olympus init");
	olympus.style.add(
		".dialog.floats {border-radius: 8px; border: 2px solid #000000; max-height: 550px; position: absolute !important; z-index: 500; background-color: #7fb4cf;} "+
		".dialog.narrow {width: 300px; min-width: 300px;} "+
		".dialog.wide {width: 500px; min-width: 500px;} "+
		".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 {top: 25px; left: 200px;}"+
		"#olympian_help p.inset {margin-left: 25px;}"+
		"#olympian_help p.inset b {margin-left: -25px; display: block;}"
	);

	// 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({
				"rel":"stylesheet",
				"href":"https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"
			})
	);

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

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

	// append the help window to the document
	$("body").append(
		$("<div/>")
			.attr({
				"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()
	);

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

	// now that all dependencies are accounted for, initialize all olympians that are present
	// the order is semi-important: athena calls hermes so the latter must be initialized first
	if(olympus.hermes) olympus.hermes.__init();
	if(olympus.athena) olympus.athena.__init();
	if(olympus.artemis) olympus.artemis.__init();
};

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
			$("#olympian_help .explain *[data-function]").each(function() {
				switch($(this).attr("data-function")) {
					case "fa-substitute": {
						$(this).addClass("fa fa-fw fa-2x "+olympus.athena.desc2fa($(this).attr("data-args")));
					}
				}
				$(this).removeAttr("data-function data-args");
			});
			// show help window
			$("#olympian_help").show();
		}
	},
	has_topic:function(topic) {
		return this.__topics.hasOwnProperty(topic);
	},
	hide:function() {
		$("#olympian_help").hide();
	}
};

window.olympus.settings = {
	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]];
		}
	},
	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;
			return settings;
		}
	},
	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({
					"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 = {
	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]);
	},
	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) {
		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);
	},
	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 = {},
			cache_timeout = 14400000; // 4 hours in milliseconds (1000 ms in one second * 60 secs * 60 mins * 4 hours)

		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) {
			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) < 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 ? ("querying for data on "+num_queried+" requesters") : "no queries necessary"));
		if(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();
});