// ==UserScript==
// @name Athena HIT search productivity enhancement
// @namespace mobiusevalon.tibbius.com
// @version 0.1
// @author Mobius Evalon
// @description Provides a number of improvements when searching for and working on HITs, including AA time, six-level TO filtering, use of HitScraper blocklist, qualification feasibility tiers, etc.
// @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
// @downloadURL none
// ==/UserScript==
this.$ = this.jQuery = jQuery.noConflict(true);
/*
function hoard(n,gid)
{
//console.log(""+n+", id "+gid);
$.get("https://www.mturk.com/mturk/previewandaccept?groupId="+gid);
if(--n > 0) setTimeout(function() {hoard(n,gid)},1500);
}
*/
$(document).ready(function() {
var athena_help_topics = {
search_options:
"
All of the items contained in the box at the top of the page are the options that configure your HIT search on Turk. This help topic will list all of them and what they do.
"+
"
KeywordPretty obvious. When used, Turk will look for HITs that contain the keyword text in requester names, HIT titles, HIT descriptions, and keywords.
"+
"
OrderThis option will instruct Turk to retrieve HITs from the database in the specified order. It is not a sort order for the results. The / icon will alter the database order as listed in the dropdown next to it. For example, 'Age (newest)' becomes 'Age (oldest)' which drastically changes the HITs you will get back.
"+
"
Minimum rewardThe least amount that a HIT must offer as a reward to be displayed as a result. This is pretty simple; if you set it to 0.05, then nothing that pays less than a nickel appears.
",
filter_options:
"
Each item in the filtering box allows you to refine the display of HITs in the result table. The icons will grey out when the option is not enabled and will be colorized when in use.
"+
"
"+
"
Blocked HITs. Athena supports both its own internal blocking and the HitScraper blocklist, if the script is installed.
"+
"
Whitelisted HITs. This includes both Athena's in-house whitelist and HitScraper's includelist, if the latter is installed.
"+
"
HITs you are qualified for. This includes only the HITs that you can accept and work on right now.
"+
"
HITs that you are not qualified for, but the missing qualifications can all be tested for. Qualifications that are granted from tests are almost always automatically and immediately scored so that you have the opportunity to discover whether or not you can become qualified.
"+
"
HITs that you are not qualified for, and at least one qualification must be requested. While a requestable qualification sounds like a non-issue on the surface, the reality is that 99% of qualification requests are completely ignored and you are wasting your time. They are rarely granted upon request, which is the only reason these are not rolled into the next qualification tier.
"+
"
HITs that you can never be qualified for. This includes Masters status (if you don't have it), location quals that you do not meet, TurkPrime exclusionary quals that you have, TurkPrime inclusionary quals that you do not have, and many other circumstances that you can never change.
"+
"
Great overall weighted TO, which is a value of 5 on the TO scale. These values are weighted 6x for pay, 3.5x for fair, 1x for fast, 0x for comm, and are rounded to the nearest integer. This means a 3.51 is color coded as 4 (good) while a 3.49 is color coded as 3 (fair).
"+
"
Good overall weighted TO, a value of 4 on the scale.
"+
"
Fair overall weighted TO, a value of 3 on the scale.
"+
"
Poor overall weighted TO, a value of 2 on the scale.
"+
"
Awful overall weighted TO, a value of 1 on the scale.
"+
"
No TO ratings for this requester exist.
"+
"
"
};
String.prototype.collapse_whitespace = function() {
// mostly only for qualifications because there's a crap ton of whitespace in
// those table cells
return this.replace(/\s+/g," ").trim();
};
String.prototype.ucFirst = function() {
return (this.charAt(0).toUpperCase()+this.slice(1));
};
function show_help_topic(topic) {
if(athena_help_topics.hasOwnProperty(topic)) {
$("#athena_help .explain").html(athena_help_topics[topic]);
$("#athena_help *[data-function]").each(function() {
switch($(this).attr("data-function")) {
case 'icon-substitute': {
$(this).addClass("fa fa-fw fa-2x "+desc2fontawesome.apply(null,$(this).attr("data-args").split(",")));
break;
}
}
$(this).removeAttr("data-function data-args");
});
$("#athena_help").show();
}
}
function retrieve_settings() {
var settings = localstorage_obj("athena_settings"),
defaults = default_settings();
if($.type(settings) === "object") {
$.each(defaults,function(k,v) {
if(!settings.hasOwnProperty(k)) settings[k] = v;
});
}
else settings = defaults;
return settings;
}
function default_settings() {
return {
interface_blocked_filter:true,
interface_highlighted_filter:true,
interface_qualified_filter:true,
interface_testable_filter:true,
interface_requestable_filter:true,
interface_impossible_filter:false,
interface_great_to_filter:true,
interface_good_to_filter:true,
interface_fair_to_filter:true,
interface_poor_to_filter:true,
interface_awful_to_filter:false,
interface_no_to_filter:true,
assist_blocked_filter:true,
assist_highlighted_filter:true,
assist_qualified_filter:true,
assist_testable_filter:true,
assist_requestable_filter:true,
assist_impossible_filter:false,
assist_great_to_filter:true,
assist_good_to_filter:true,
assist_fair_to_filter:true,
assist_poor_to_filter:true,
assist_awful_to_filter:false,
assist_no_to_filter:true
};
}
function update_settings(key,val) {
var settings = retrieve_settings();
if(settings.hasOwnProperty(key)) settings[key] = val;
localStorage.athena_settings = JSON.stringify(settings);
}
function setting_name(desc) {
return (($("body").hasClass("athena_interface") ? "interface" : "assist")+"_"+desc+"_filter");
}
function setting_state(desc) {
return script_settings[setting_name(desc)];
}
function plural(n) {
// returns an s if the number is not 1. just for pretty display so that it
// can say e.g. "1 day" instead of "1 days"
if($.type(n) !== "number") n = +n;
if(n != 1) return "s";
return "";
}
function dhms(secs) {
// i capped the precision of this function on purpose so that it fits on the
// capsule nicely. all it does is return the highest time multiple rounded
// to the nearest tenth
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 = (secs/multiple);
return (""+zeroes(units)+" "+name+plural(units));
}
if($.type(secs) !== "number") secs = Math.round(secs*1);
if(secs >= 86400) return output(86400,"day"); // (""+f(secs/86400)+" days");
else if(secs >= 3600) return output(3600,"hour"); // (""+f(secs/3600)+" hours");
else if(secs >= 60) return output(60,"minute"); // (""+f(secs/60)+" minutes");
else return output(1,"second"); // (""+secs+" seconds");
}
function impossible_request_qual(n) {
// determines situations where a requestable qualification is impossible to obtain
// this function is only called on qualifications that you do not have
var LT = "lt",
GT = "gt";
function comparator() {
var d = (n.indexOf("not") > -1),
e = +n.match(/(\d{1,3})$/i)[1];
if(n.indexOf("greater than") > -1) {
if(d) return [LT,e+1];
else return [GT,e];
}
if(n.indexOf("less than") > -1) {
if(d) return [GT,e-1];
else return [LT,e];
}
}
if(/exc: \[[\d-]+\]/i.test(n)) return true; // turkprime exclusionary quals are removed at the requester's leisure and never on demand
if(/inc: \[[\d-]+\]/i.test(n)) return true; // turkprime inclusionary quals are granted for finishing qualification HITs and cannot be requested
if(/masters has been granted$/i.test(n)) return true; // requests for masters status are always ignored without exception
// if(/^location is/i.test(n)) return true; // you cannot change the location on your profile so these HITs are impossible to qualify for by extension
// if(/^hit approval rate/i.test(n)) {
// var o = comparator();
// if(o[0] === GT && o[1] === 100) return true; // your approval rate cannot be greater than 100 (sounds obvious but this qual happens)
// if(o[0] === LT) return true; // might as well be impossible, since i doubt you want to torpedo your approval rate on purpose to get under a specified percentage
// }
// if(/^total approved hits/i.test(n)) {
// var p = comparator();
// if(p[0] === LT) return true; // your approved hit number cannot go down
// }
// an assortment of specific quals that are known to be impossible
if(/^ibotta/i.test(n)) return true;
return false;
}
function filter_hits() {
// steadily reduce the amount of visible hits, first by HitScraper blocklist, then by qualification selection, and then by TO average
var hs_blocked = localStorage.scraper_ignore_list;
if($.type(hs_blocked) === "string" && hs_blocked.split("^").length) {
var $hsb_tmp = $(".athena_contains_hit"),
hsb_patterns = [],
hsb_plaintext = [];
hs_blocked.split("^").filter(function(val) {
val = val.collapse_whitespace().toLowerCase();
if(val.indexOf("*") > -1) hsb_patterns.push(val.replace(/[-[\]{}()+?.,\\^$|#\s]/g,"\\$&").replace(/\*+/g,".+"));
else hsb_plaintext.push(val);
return false;
});
$.each($hsb_tmp,function(idx,hit) {
var requester = $("span.requesterIdentity",hit).text().collapse_whitespace().toLowerCase(),
title = $("a.capsulelink",hit).text().collapse_whitespace().toLowerCase(),
blocked_hit = ( // javascript short circuits its || conditionals, so the regular expression won't even execute if the plain text matches
(hsb_plaintext.indexOf(requester) > -1) || (hsb_plaintext.indexOf(title) > -1) ||
hsb_patterns.filter(function(val) {
var regex = new RegExp(val,"gi");
if(regex.test(requester) || regex.test(title)) return true;
}).length
);
if(blocked_hit && $("#athena_filter .watchlists .fa-ban").hasClass("enabled")) $(hit).addClass("hs_blocked");
else $(hit).removeClass("hs_blocked");
//if($("#athena_filter .watchlists .fa-ban").first().hasClass("enabled")) $(hit).addClass("hidden");
//else $(hit).removeClass("hidden");
});
}
$("#athena_filter .qualifications span.fa[data-filter]").each(function() {
// select all table rows that were not hidden by the hitscraper blocklist
var $tmp = $(".athena_hit_table.ci_"+$(this).attr("data-filter")).closest(".athena_contains_hit").filter(":not(.hs_blocked)");
if(!$(this).hasClass("enabled")) $tmp.addClass("ci_qual_filter");
else $tmp.removeClass("ci_qual_filter");
});
$("#athena_filter .to_ratings input[type='checkbox'][data-filter]").each(function() {
// select all table rows that were not hidden by hitscraper or for qualification status
var $tmp = $(".athena_hit_title."+$(this).attr("data-filter")+"_rating").closest(".athena_contains_hit").filter(":not(.hs_blocked):not(.ci_qual_filter)");
if(!$(this).prop("checked")) $tmp.addClass("ci_to_filter");
else $tmp.removeClass("ci_to_filter");
});
// highlight any HITs that remain if includelisted by HitScraper
var hs_highlighted = localStorage.scraper_include_list;
if($.type(hs_highlighted) === "string" && hs_highlighted.split("^").length) {
var $hsh_tmp = $(".athena_contains_hit").filter(":not(.hs_blocked):not(.ci_qual_filter):not(.ci_to_filter)"),
hsh_plaintext = [];
hs_highlighted.split("^").filter(function(val) {
hsh_plaintext.push(val.collapse_whitespace().toLowerCase());
});
$.each($hsh_tmp,function(idx,hit) {
var requester = $("span.requesterIdentity",hit).text().collapse_whitespace().toLowerCase(),
title = $("a.capsulelink",hit).text().collapse_whitespace().toLowerCase(),
highlighted_hit = (hsh_plaintext.indexOf(requester) > -1) || (hsh_plaintext.indexOf(title) > -1);
if(highlighted_hit && $("#athena_filter .watchlists .fa-star").hasClass("enabled")) $(".athena_hit_table",hit).addClass("hs_highlighted");
else $(".athena_hit_table",hit).removeClass("hs_highlighted");
});
}
}
function color_code_by_to(info)
{
function to_average(attrs) {
// decimals are valid, as is zero (which removes that dimension from the average)
var weight = {
pay:5.5,
fair:3,
fast:1,
comm:0
},
avg = 0,
divisor = 0;
$.each(attrs,function(key,val) {
var sum = (val*weight[key]);
if(sum > 0) {
avg += sum;
divisor += weight[key];
}
});
return (avg/divisor);
}
function to_avg_to_text(s) {
switch(s) {
case 5: return "great";
case 4: return "good";
case 3: return "fair";
case 2: return "poor";
case 1: return "awful";
case 0: return "no";
}
}
if($.type(info) === "object") {
$(".athena_hit_list .athena_hit_table").each(function() {
var rid = $("span.requesterIdentity",this).parent().attr("href").match(/&requesterId=([a-z0-9]{12,14})$/i)[1],
has_to = ($.type(info[rid]) === "object" && info[rid].hasOwnProperty("attrs"));
$(".athena_hit_title",this)
.addClass((has_to ? to_avg_to_text(Math.round(to_average(info[rid].attrs))) : "no")+"_to_rating")
.find("tr").first().after(
$("
").append(
$("
")
.attr("colspan","3")
.append(
$("")
.attr({
"href":("https://turkopticon.ucsd.edu/"+rid),
"target":"_blank"
})
.append(
document.createTextNode(has_to ? (info[rid].reviews+" review"+plural(info[rid].reviews)+" ["+info[rid].attrs.pay+" pay | "+info[rid].attrs.fair+" fair | "+info[rid].attrs.fast+" fast | "+info[rid].attrs.comm+" comm] ") : "No Turkopticon data"),
$("")
.css("font-weight","bold")
.text((has_to && info[rid].tos_flags*1) > 0 ? (info[rid].tos_flags+" TOS") : "")
)
)
)
);
$("span.reward",this).addClass((has_to ? to_avg_to_text(Math.round(info[rid].attrs.pay*1)) : "no")+"_to_rating");
});
}
else console.log("retrieved TO info is malformed");
}
function desc2fontawesome(d) {
switch(d) {
case "blocked": return "fa-ban";
case "highlighted": return "fa-star";
case "qualified": return "fa-check";
case "testable": return "fa-pencil";
case "requestable": return "fa-lock";
case "impossible": return "fa-warning";
}
}
function athena_filter_bar() {
function toggle_element(desc) {
var $label = $("").attr("class","toggle_element");
switch(desc) {
case "blocked": case "highlighted": case "qualified": case "testable": case "requestable": case "impossible": {
$label
.attr("title",desc.ucFirst())
.append(
$("")
.attr({
"class":("fa fa-2x fa-fw "+desc2fontawesome(desc)+" "+(setting_state(desc) ? "enabled" : "disabled")),
"data-filter":desc
})
.click(function() {
$(this).toggleClass("enabled disabled");
update_settings(setting_name($(this).attr("data-filter")),$(this).hasClass("enabled"));
filter_hits();
})
);
break;
}
case "great_to": case "good_to": case "fair_to": case "poor_to": case "awful_to": case "no_to": {
$label
.addClass(desc+"_rating")
.attr("title",(desc.slice(0,desc.indexOf("_")).ucFirst()+" TO rating"))
.append(
$("")
.attr({
"type":"checkbox",
"data-filter":desc
})
.prop("checked",setting_state(desc))
.click(function() {
update_settings(setting_name(desc),$(this).prop("checked"));
filter_hits();
})
);
break;
}
}
return $label;
}
var $filter_bar = $("")
.attr("id","athena_filter")
.append(
$("")
.attr("class","watchlists")
.append(
toggle_element("blocked"),
toggle_element("highlighted")
),
$("")
.attr("class","qualifications")
.append(
toggle_element("qualified"),
toggle_element("testable"),
toggle_element("requestable"),
toggle_element("impossible")
),
$("")
.attr("class","to_ratings")
.append(
toggle_element("great_to"),
toggle_element("good_to"),
toggle_element("fair_to"),
toggle_element("poor_to"),
toggle_element("awful_to"),
toggle_element("no_to")
)
);
if(!$("body").hasClass("athena_interface")) $filter_bar.append(
$("")
.attr({
"class":"fa fa-2x fa-fw fa-question-circle-o",
"title":"Filtering help"
})
.click(function() {
show_help_topic("filter_options");
})
);
return $filter_bar;
}
function turkopticon() {
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 (60 secs * 60 mins * 4 hours * 1000)
function mirror_domain(s) {
return s.match(/^https{0,1}:\/\/(.+?)\//i)[1];
}
function exit() {
color_code_by_to(query_result);
filter_hits();
}
// 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 = (localstorage_obj("mci_to_cache") || {});
if($.type(attrs) !== "object") attrs = {};
attrs.cache_time = new Date().getTime();
to_cache[rid] = attrs;
localStorage.mci_to_cache = JSON.stringify(to_cache);
}
function get_cache(rid) {
var to_cache = localstorage_obj("mci_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;
}
}
function request(url) {
$.ajax({
async:true,
method:"GET",
url:(url+query_rids.join(","))
})
.fail(function() {
console.log("attempt to gather Turkopticon data from "+mirror_domain(url)+" mirror failed");
var idx = (to_mirrors.indexOf(url)+1);
if(idx < to_mirrors.length) {
console.log("attempting Turkopticon data request from mirror "+mirror_domain(to_mirrors[idx])+"...");
request(to_mirrors[idx]);
}
else {
console.log("attempts to gather Turkopticon data from all available mirrors has failed");
exit();
}
})
.done(function(response) {
var jsobj = JSON.parse(response);
if($.type(jsobj) === "object") {
console.log("successfully queried Turkopticon data from "+mirror_domain(url)+" mirror");
$.each(jsobj,function(rid,attrs) {
set_cache(rid,attrs);
query_result[rid] = attrs;
});
}
else console.log("turkopticon query was successful but data was malformed");
exit();
});
}
// check the cache for relevant data we can use and query for the rest
$.each(arguments,function(k,v) {
var cached = get_cache(v);
if($.type(cached) === "object") query_result[v] = cached;
else query_rids.push(v);
});
if(query_rids.length) {
console.log("querying turkopticon for data on "+query_rids.length+" requesters");
request(to_mirrors[0]);
}
else exit();
}
function json_obj(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;
}
function localstorage_obj(key) {
var obj = json_obj(localStorage.getItem(key));
if(typeof obj !== "object") localStorage.removeItem(key);
return obj;
}
function athena_dependencies() {
// append the fontawesome stylesheet to the page if it does not exist
if(!$("link[rel='stylesheet'][href$='font-awesome.min.css']").length) $("head").append(
$("")
.attr({
"rel":"stylesheet",
"href":"https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css"
})
);
$("head").append(
$("")
.attr("type","text/css")
.text(
".ci_qual {width: 16px; height: 16px; font-size: 150%; text-align: center;}"+
".ci_qual.fa-check, #athena_filter .qualifications .fa-check.enabled, #athena_help .fa-check {color: #050;}"+
".ci_qual.fa-times, .ci_qual.fa-warning, #athena_filter .watchlists .fa-ban.enabled, #athena_filter .qualifications .fa-warning.enabled, #athena_help .fa-ban, #athena_help .fa-warning {color: #a45555;}"+
"#athena_filter .watchlists .fa-star.enabled, #athena_help .fa-star {color: #ffd37b;}"+
".ci_qual.fa-lock, #athena_filter .qualifications .fa-lock.enabled, #athena_help .fa-lock {color: #eebb56; text-decoration: none;}"+
".ci_qual.fa-pencil, #athena_filter .qualifications .fa-pencil.enabled, #athena_help .fa-pencil {color: #005; text-decoration: none;}"+
".ci_impossible {opacity: 0.5;}"+
".athena_hit_title.great_to_rating, .reward.great_to_rating, .toggle_element.great_to_rating {background-color: #b8f188;}"+
".athena_hit_title.good_to_rating, .reward.good_to_rating, .toggle_element.good_to_rating {background-color: #ebf188;}"+
".athena_hit_title.fair_to_rating, .reward.fair_to_rating, .toggle_element.fair_to_rating {background-color: #ffd37b;}"+
".athena_hit_title.poor_to_rating, .reward.poor_to_rating, .toggle_element.poor_to_rating {background-color: #f5b3b3;}"+
".athena_hit_title.awful_to_rating, .reward.awful_to_rating, .toggle_element.awful_to_rating {background-color: #de9aff;}"+
"#athena_filter {text-align: center;}"+
"#athena_filter > div {display: inline-block;}"+
"#athena_filter .watchlists, #athena_filter .qualifications {margin-right: 24px;}"+
"#athena_filter .to_ratings label, #athena_help .toggle_element {display: inline-block; width: 24px; height: 24px; text-align: center;}"+
"#athena_filter .to_ratings .no_to_rating, #athena_help .no_to_rating {background: linear-gradient(to right,#ccdde9,#ccc)}"+
"#athena_filter .to_ratings input[type='checkbox'] {width: 16px; height: 16px;}"+
"#athena_filter .watchlists .fa.disabled, #athena_filter .qualifications .fa.disabled {opacity: 0.75; color: #ccc;}"+
"#athena_filter .fa-question-circle-o {margin-left: 15px;}"+
".hs_blocked {opacity: 0.5;}"+
".hs_highlighted {border: 3px dashed #0f0;}"+
".ci_qual_filter, .ci_to_filter, .hs_blocked {display: none !important;}"+
"body.athena_interface {background-color: #eee; margin: 0px; padding: 5px;} "+
"#athena_search fieldset label {display: inline-block;}"+
"#athena_search fieldset label:not(:last-of-type) {margin-right: 15px;}"+
"#athena_search fieldset label input, #athena fieldset label select {margin-left: 6px;} "+
"#athena_search fieldset label input[type='number'] {width: 50px;}"+
"#athena_search fieldset legend .fa-plus-square-o, #athena fieldset legend .fa-minus-square-o, #athena legend .fa-question-circle-o {cursor: pointer;}"+
".athena_interface .dialog.floats {background-color: #eee !important; border: 2px solid #000 !important;}"+
".dialog.floats {background-color: #a5ccdd; border: 2px solid #5c9ebf; border-radius: 8px; max-height: 450px; padding: 5px; position: absolute !important; top: 0px; left: 100px; z-index: 100;} "+
".dialog.narrow {width: 400px; min-width: 400px;} "+
".dialog.wide {width: 650px; min-width: 650px;} "+
".dialog .scrolling-content {max-height: 350px; overflow-y: auto;} "+
".dialog .actions {margin: 10px auto 0px auto; padding: 0px; text-align: center; display: block;} "+
".dialog .actions input:not(:last-of-type) {margin-right: 15px;} "+
".dialog .head {padding: 0px; margin: 0px auto 10px auto; font-size: 150%; font-weight: bold; width: 100%; text-align: center; cursor: move;} "+
"#athena_help p.inset {margin-left: 25px;}"+
"#athena_help p.inset b {margin-left: -25px; display: block;}"+
"#athena_help table tr td:nth-child(1) {vertical-align: top; text-align: center;}"+
"#athena_help table tr td:nth-child(2) {padding-bottom: 15px;}"
)
);
$("body").append(
$("")
.attr({
"id":"athena_help",
"class":"dialog narrow floats"
})
.append(
$("")
.attr("class","head")
.text("Athena help"),
$("")
.attr("class","scrolling-content")
.append(
$("").attr("class","explain")
),
$("")
.attr("class","actions")
.append(
$("")
.text("Close")
.click(function() {
$("#athena_help").hide();
})
)
)
.hide()
.draggable({handle:"h1.head"})
);
}
var script_settings = retrieve_settings();
if(/(?:&|\?)athena$/i.test(window.location.href)) {
var help_mode = false;
// the athena scraper interface proper
$("head").html("").append(
$("").text("Athena HIT scraper")
);
$("body").html("");
athena_dependencies();
$("body").removeAttr("onload").addClass("athena_interface").append(
$("")
.attr("id","athena")
.append(
$("").text("Athena HIT scraper"),
$("").attr("id","athena_search").append(
$("").append(
$("")
.attr("class","fa fa-fw fa-lg fa-minus-square-o")
.click(function() {
$(this).toggleClass("fa-minus-square-o fa-plus-square-o");
if($(this).hasClass("fa-plus-square-o")) $("#athena_search").children().not("legend").hide();
else $("#athena_search").children().not("legend").show();
}),
document.createTextNode("Search configuration"),
$("")
.attr("class","fa fa-fw fa-lg fa-question-circle-o")
.click(function() {
show_help_topic("search_options");
})
),
$("").append(
document.createTextNode("Keyword:"),
$("").attr({
"id":"athena_min_to_pay",
"type":"text"
})
),
$("").append(
document.createTextNode("Order:"),
$("")
.attr("id","athena_search_order")
.append(
$("")
.attr("value","date")
.text("Age (newest)"),
$("")
.attr("value","reward")
.text("Reward (most)"),
$("")
.attr("value","batch")
.text("Batch size (most)"),
$("")
.attr("value","date")
.text("Title (A-Z)")
),
$("")
.attr("class","fa fa-lg fa-caret-square-o-down")
.click(function() {
function desc(dir,type) {
switch(type) {
case "Age": return (dir === "asc" ? "oldest" : "newest");
case "Reward": case "Batch size": return (dir === "asc" ? "least" : "most");
case "Title": return (dir === "asc" ? "Z-A" : "A-Z");
}
}
$(this).toggleClass("fa-caret-square-o-down fa-caret-square-o-up");
var dir = ($(this).hasClass("fa-caret-square-o-up") ? "asc" : "desc");
$("select#athena_search_order option").each(function() {
var type = $(this).text().slice(0,$(this).text().indexOf("(")-1);
$(this).text(type+" ("+desc(dir,type)+")");
});
})
),
$("").append(
document.createTextNode("Reward:"),
$("").attr({
"id":"athena_min_reward",
"type":"number",
"min":"0",
"step":"0.05"
})
),
$("").append(
document.createTextNode("Batch size:"),
$("").attr({
"id":"athena_min_batch_size",
"type":"number",
"min":"1",
"step":"25"
})
),
$("").append(
document.createTextNode("TO pay:"),
$("").attr({
"id":"athena_min_to_pay",
"type":"number",
"min":"0",
"max":"5"
})
),
$("").append(
document.createTextNode("Skip unqualified:"),
$("").attr({
"id":"athena_skip_unqualified",
"type":"checkbox"
})
),
$("").append(
$("").attr({
"class":"fa fa-fw fa-lg fa-maxcdn",
"title":"Only Masters HITs"
})
)
),
$("").append(
$("").append(
$("")
.attr("class","fa fa-fw fa-lg fa-minus-square-o")
.click(function() {
$(this).toggleClass("fa-minus-square-o fa-plus-square-o");
if($(this).hasClass("fa-plus-square-o")) $("#athena_filter").hide();
else $("#athena_filter").show();
}),
document.createTextNode("Result filtering"),
$("")
.attr("class","fa fa-fw fa-lg fa-question-circle-o")
.click(function() {
show_help_topic("filter_options");
})
),
athena_filter_bar()
)
)
);
}
else {
// assistive functions on mturk pages
athena_dependencies();
// hit filtering options
$("form#sortresults_form").after(
athena_filter_bar()
);
// separate "first assigned hit" and "list my assigned hits" links. i find the
// latter useful when lots of pandas go off
$("div#subtabs")
.find("a:contains(HITs Assigned To You)").first().replaceWith(
$("").append(
document.createTextNode("Assigned [ "),
$("")
.attr({
"class":"subnavclass",
"href":"/mturk/myhits?first"
})
.text("First"),
document.createTextNode(" | "),
$("")
.attr({
"class":"subnavclass",
"href":"/mturk/myhits"
})
.text("List"),
document.createTextNode(" ]")
)
)
.end().end().find("a:contains(HITs Available To You)").first().text("Qualified");
// just adds page total earnings when you're looking through your status
if(document.location.href.indexOf("/statusdetail?encodedDate") > -1) {
var total = 0,
$table = $("#dailyActivityTable");
$table.find("tr:not(:first) td:nth-child(3)").each(function() {total += ($(this).text().slice(1)*1);});
$table.before(
$("")
.attr("id","total")
.css({
"text-align":"center",
"font-weight":"bold",
"font-size":"120%"
})
.text("Earnings this page: $"+total.toFixed(2))
);
}
// remove the stupid profile tasks box (since the x in the corner doesn't
// make it go away)
$("div.info-message-container").first().remove();
// display auto approval time on the capsule if the information is available
// (only on accept/continue from what i can tell)
var $aa = $("input[name='hitAutoAppDelayInSeconds']").first();
if($aa.length) {
var reward = +$("span.reward",$("a[id*='reward.tooltip']").parent().next()).text().slice(1),
hits = +$("a[id*='number_of_hits.tooltip']").parent().next().text().collapse_whitespace(),
group_val = (reward*hits).toFixed(2);
$("a[id*='qualifications.tooltip'], a[id*='qualificationsRequired.tooltip']").first().closest("table").parent()
.attr("colspan","6")
.after(
$("
")
.attr({
"align":"left",
"valign":"top",
"class":"capsule_field_text",
"nowrap":""
})
.text(dhms($aa.val()))
);
}
// tag critical elements with class names for later, and insert all interface elements in them while we're at it
$("a.capsulelink")
.before(
$("").attr("class","fa fa-fw fa-user").css({"width":"16px","height":"16px"}),
$("").attr("class","fa fa-fw fa-file").css({"width":"16px","height":"16px"})
)
.closest("table").parent().addClass("athena_hit_title")
.closest("table").closest("tr").each(function() {
$(this)
.addClass("athena_contains_hit")
.children("td:first-child").children("table:first-child").addClass("athena_hit_table");
})
.closest("table").addClass("athena_hit_list");
// puts an accept link on each capsule
if(window.location.href.indexOf("/mturk/myhits") === -1) {
$(".capsulelink:not([id])").each(function() {
var $preview = ($(this).children("a[href*='groupId'], a[href*='hitGroupId']").first());
if($preview.length) {
var id = $preview.attr("href").slice(-30),
cannot_preview = ($preview.attr("href").indexOf("hitGroupId") > -1);
$(this).empty().append(
$('')
.text("View a HIT in this group")
.attr({
"target":"_blank",
"href":("/mturk/preview?groupId="+id),
"title":(cannot_preview ? "The requester does not allow unqualified workers to preview this HIT" : "Preview this HIT without accepting it")
})
.css("text-decoration",(cannot_preview ? "line-through" : "underline")),
document.createTextNode(" | "),
$('')
.text("Accept")
.attr({
"target":"_blank",
"href":("/mturk/previewandaccept?groupId="+id),
"title":(cannot_preview ? "You are not qualified to work on this HIT" : "Accept this HIT and begin working on it")
})
.css("text-decoration",(cannot_preview ? "line-through" : "underline")),
document.createTextNode(" | "),
$('')
.text("Hoard")
.click(function() {console.log("Hoard link doesn't do anything yet");})
.css("text-decoration",(cannot_preview ? "line-through" : "underline"))
);
}
});
// replaces the lengthy, fatiguing, and too-similar-to-tell-apart-at-a-glance
// text of the qualification prerequisites with very simple icons. green
// checkmark for meeting the qual, red x for not meeting it, blue question mark
// to request quals, and an orange warning triangle for impossible qualifications
$("a[id*='qualificationsRequired.tooltip']").closest("table").each(function() {
function qual_precedence(c,n) {
if(c === "impossible" || n === "impossible") return "impossible"; // impossible trumps everything
else if(n === "requestable") {
if(!c || c === "testable" || c === "qualified") return n; // requestable trumps testable and qualified
}
else if(n === "testable") {
if(!c || c === "qualified") return n; // testable trumps qualified
}
else if(n === "qualified" && !c) return n;
return c; // if we've gotten here then the new status does not trump the old status
}
var qual_status = "";
if($(this).find("td:nth-child(2)").text().trim() === "None") qual_status = "qualified";
else $("tr:not(:first-of-type) td:nth-child(3)",$(this)).each(function() {
var $elem,
qual_desc = $(this).prev().prev().text().collapse_whitespace(),
qual_tooltip = $(this).text().collapse_whitespace(),
meet_qual = ($(this).text().indexOf("You meet this qualification") > -1);
if(!meet_qual) {
if(qual_tooltip.indexOf("Qualification test") > -1) {
$elem = $("").attr({"class":desc2fontawesome("testable"),"href":$(this).children("a").first().attr("href"),"target":"_blank"});
qual_status = qual_precedence(qual_status,"testable");
}
else if(qual_tooltip.indexOf("Request Qualification") > -1 && !impossible_request_qual(qual_desc)) {
$elem = $("").attr({"class":desc2fontawesome("requestable"),"href":$(this).children("a").first().attr("href"),"target":"_blank"});
qual_status = qual_precedence(qual_status,"requestable");
}
else {
$elem = $("").attr({"class":desc2fontawesome("impossible")});
qual_status = qual_precedence(qual_status,"impossible");
}
}
else {
$elem = $("").attr("class",desc2fontawesome("qualified"));
qual_status = qual_precedence(qual_status,"qualified");
}
$elem
.addClass("ci_qual fa fa-fw")
.attr("title",(qual_status === "impossible" ? "This qualification is impossible" : qual_tooltip));
$(this).empty().append($elem);
});
$(this).closest(".athena_hit_table").addClass("ci_"+qual_status);
});
// expand the info for all HITs that do not have impossible quals
$(".athena_hit_table:not(.ci_impossible) a.capsulelink").each(function() {
$(this).get(0).click();
});
}
// color code tables by to rating
var rids = [];
$("span.requesterIdentity").parent().each(function() {
var cid = $(this).attr("href").match(/&requesterId=([a-z0-9]{12,14})$/i)[1],
exists = (rids.indexOf(cid) > -1);
if(!exists) rids.push(cid);
});
if(rids.length) turkopticon.apply(null,rids);
}
});