您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Common features shared amongst all Olympian scripts.
// ==UserScript== // @name Mount Olympus // @namespace mobiusevalon.tibbius.com // @version 2.0-7 // @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 // @require https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.26.0/js/jquery.tablesorter.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.__version = "2.0-7"; window.olympus.__href = "https://greasyfork.org/en/scripts/23092-mount-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, bayesian_reviews:75, bayesian_average:2.75 }; window.olympus.__init = function() { console.log("olympus init"); Array.prototype.contains = function(item) { return (this.indexOf(item) > -1); }; String.prototype.collapseWhitespace = function() { return this.replace(/\s{2,}/g," ").trim(); }; String.prototype.contains = function(substring) { return (this.indexOf(substring) > -1); }; String.prototype.ucFirst = function() { return (this.charAt(0).toUpperCase()+this.slice(1)); }; 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 {border-radius: 8px 0 0 8px; 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 .sublabel {width: 50px; display: inline-block; margin-right: 5px; text-align: right;}"+ "#olympian_settings .container .fa-toggle-off, #olympian_settings .container .fa-toggle-on {width: 47px; text-align: center;}"+ "#olympian_settings .container .fa-toggle-off {color: #000;}"+ "#olympian_settings .container .fa-toggle-on {color: #fff;}"+ "#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;}"+ ".olympian_identifier {display: block; margin: 5px auto 10px auto; text-align: center; font-size: 150%; font-weight: bold;}"+ ".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" }) ); // 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() ); this.settings.init(this); // 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 olympus.settings.init(olympus[olympian]); //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","sublabel") .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 has a big effect on the final average by making values more important "+ "(with values greater than 1) or less important (with values between 0 and 1.) Most turkers choose to stress "+ "pay and fairness over speed and communication with values like 6, 4, 1, 1 respectively, which makes the "+ "computed average lean very heavily on the former two attributes and very little on the latter two. If you do "+ "not want to weight the Turkopticon attributes, then each of these should be set to 1." }) ), olympus.settings ._gen_option_wrapper({ name:"Bayesian settings", elements:3 }) .append( $("<span/>") .attr("class","sublabel") .text("Enable:"), olympus.settings._gen_option({ option:"bayesian_to", type:"checkbox", value:olympus.settings.get(olympus,"bayesian_to") }), $("<span/>") .attr("class","sublabel") .text("Average:"), olympus.settings._gen_option({ option:"bayesian_average", "type":"number", value:olympus.settings.get(olympus,"bayesian_average") }), $("<br/>"), $("<span/>") .attr("class","sublabel") .text("Reviews:") .css("margin-left","262px"), olympus.settings._gen_option({ option:"bayesian_reviews", "type":"number", value:olympus.settings.get(olympus,"bayesian_reviews") }) ) .add( olympus.settings._gen_desc({ desc: "The simple 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 occurs when the average is higher than a specified amount and/or when "+ "there are very few reviews that factor into that average. Bayesian functions are mostly guesswork/personal "+ "preference by design which is the reason you are allowed to tinker with these if you so choose.<br><br>"+ "The Bayesian average is the point where you doubt the accuracy above that amount. In a perfect system, "+ "this would be the average of every input available (in this case, averaging every requester's averages), so in the "+ "absence of that information we will simply have to guess. In the case of Turk specifically, we all know that there "+ "are very few requesters that are not crap, so we doubt glowing reviews simply by knowing this fact.<br><br>"+ "The Bayesian reviews is the point where we think we have enough data to make reasonable assumptions. This prevents any "+ "requester with a low number of reviews from getting a high score. A requester with a single all fives review will "+ "get a Bayesian average around 2.50 instead of the 5.00 that Turkopticon assigns them." }) ), 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) { var olympian = (tab === "olympus" ? window.olympus : olympus[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( $("<a/>") .attr({ "class":"olympian_identifier", "href":olympian.__href, "target":"_blank" }) .text(olympian.__name.ucFirst()+" "+olympian.__version), (olympian.hasOwnProperty("__configurable") ? olympian.__configurable() : olympus.settings.explain({ type:"plain", desc: "This Olympian is installed, but has no configurable options to appear here." })) ); olympus.utilities.parse_deferred_functions($("#olympian_settings .container")); }, commit_page:function() { var tab = $("#olympian_settings .sidebar .tab.active").attr("id").slice(0,-4), olympian = (tab === "olympus" ? window.olympus : olympus[tab]); if($.type(olympian) === "object" && olympian.hasOwnProperty("__parse_settings")) { var 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; } } }); 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) { if($.type(olympian) === "object" && olympian.hasOwnProperty("__name")) { // this makes sure that any new options that are added from later updates are automatically // loaded into the existing saved settings as their default values if(olympian.hasOwnProperty("default_settings")) { 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 } } // add a tab to the settings window for this olympian $("#olympian_settings .sidebar").append( $("<div/>") .attr({ "class":"tab", "id":(olympian.__name+"_tab") }) .text(olympian.__name.ucFirst()) .click(function() { olympus.settings.change_tab($(this).attr("id").slice(0,-4)); }) ); } }, open:function(source) { 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?:\/\/([^/$]+)/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 to '"+domain_name(url)+"' 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)); }, capsule_info:function($element) { function scrape_from_tooltip() { var value = ""; $.each(arguments,function(index,text) { var $anchor = $("a[id^='"+text+".tooltip']",$element); if($anchor.length) { value = $anchor.parent().next().text().collapseWhitespace(); return false; // the only way to break $.each } }); return value; } var tokens = { // basic HIT info that can be scraped off the page hit_name:$("a.capsulelink[href='#']",$element).first().text().collapseWhitespace(), hit_id:olympus.utilities.href_group_id($("a[href*='roupId=']",$element).first().attr("href") || window.location.href), // groupid does not appear in preview hit_desc:(scrape_from_tooltip("description") || "None"), // description does not appear in preview hit_time:scrape_from_tooltip("duration_to_complete","time_left"), hits_available:scrape_from_tooltip("number_of_hits"), hit_reward:$("span.reward",$element).text().collapseWhitespace(), requester_name:$("a[href*='selectedSearchType=hitgroups']",$element).first().text().collapseWhitespace(), requester_id:olympus.utilities.href_requester_id($("a[href*='requesterId']",$element).first().attr("href")) }; // link properties for convenience, since these are long URLs that only use one bit of previously collected info tokens.preview_link = ("https://www.mturk.com/mturk/preview?groupId="+tokens.hit_id); tokens.panda_link = ("https://www.mturk.com/mturk/previewandaccept?groupId="+tokens.hit_id); tokens.requester_hits = ("https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+tokens.requester_id); tokens.contact_requester = ("https://www.mturk.com/mturk/contact?requesterId="+tokens.requester_id+"&requesterName="+tokens.requester_name); tokens.to_reviews = ("https://turkopticon.ucsd.edu/"+tokens.requester_id); // parse qualifications var $qual_anchor = $("a[id^='qualificationsRequired.tooltip'], a[id^='qualifications.tooltip']",$element).first(); if($qual_anchor.parent().next().text().collapseWhitespace() === "None") tokens.quals = "None"; else { if($qual_anchor.attr("id").contains("Required")) { // viewing list of HITs var quals = []; $("tr:not(:first-of-type) td:first-of-type",$qual_anchor.closest("table")).each(function() {quals.push($(this).text().collapseWhitespace());}); tokens.quals = quals.join("; "); } else tokens.quals = $qual_anchor.parent().next().text().collapseWhitespace(); // previewing HIT where quals are already semicolon-delimited } return tokens; }, 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_id:function(href) { // when i don't know which one i have return (this.href_requester_id(href) || this.href_group_id(href)); }, href_requester_id:function(href) { if($.type(href) === "string") { href = href.match(/requesterId=([^&\s]+)/i); if($.type(href) === "array") return href[1]; } }, html_friendly:function(string) { return string.collapseWhitespace().replace(/\s/g,"_"); }, 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 "feasibility2desc": { $(this).replaceWith(olympus.athena.feasibility2desc($(this).attr("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 ""; }, to_average:function(info) { function confidence(avg,ttl) { var rr = (olympus.settings.get(olympus,"bayesian_reviews")*1); return ((ttl/(ttl+rr))*avg+(rr/(ttl+rr))*olympus.settings.get(olympus,"bayesian_average")); } var sum = 0, divisor = 0, average = 0; $.each(info.attrs,function(key,val) { var weight = (olympus.settings.get(olympus,"to_"+key+"_weight")*1), total = (val*weight); if(total > 0) { sum += total; divisor += weight; } }); average = (sum/divisor); return (olympus.settings.get(olympus,"bayesian_to") ? confidence(average,info.reviews) : average); }, 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 = {}, deferred_cache = {}; function exit() { cache_commit(); if($.type(callback) === "function") callback.call(scope,query_result); } // turkopticon caching functions of this script reduce overhead ajax calls to the api // and instead stash data on the local system, if the user has allowed it. i personally // think a couple hundred kilobytes on your computer is well worth the performance boost function cache_commit() { // commit the deferred information from cache_set if(Object.keys(deferred_cache).length) { var to_cache = (olympus.utilities.localstorage_obj("olympian_to_cache") || {}); $.each(deferred_cache,function(rid,attrs) { if($.type(attrs) !== "object") attrs = {}; attrs.cache_time = new Date().getTime(); to_cache[rid] = attrs; }); localStorage.olympian_to_cache = JSON.stringify(to_cache); } } function cache_get(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]; // when turkopticon is disabled, any data is better than no data so cached information is used // regardless of age. otherwise, if turkopticon is enabled, there is a maximum age imposed // on the cached results as defined in the options that the user has set if(!olympus.settings.get(olympus,"query_turkopticon") || new Date().getTime() - (attrs.cache_time*1) < olympus.settings.get(olympus,"to_cache_timeout")) return attrs; } } function cache_set(rid,attrs) { // so that all new cached data is stored once instead of firing a storage event infinity times if(olympus.settings.get(olympus,"use_to_cache")) deferred_cache[rid] = attrs; } // check the cache for relevant data we can use and query for the rest $.each(rids,function(k,v) { var cached = cache_get(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) { cache_set(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(); });