// ==UserScript==
// @name Mount Olympus
// @namespace mobiusevalon.tibbius.com
// @version 1.3
// @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 = {};
// 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(
"#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;} "+
".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;}"+
".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({
"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 it must be initialized first
if(olympus.hermes) olympus.hermes.__init();
if(olympus.harpocrates) olympus.harpocrates.__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]);
},
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) {
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();
});