// ==UserScript==
// @name Athena HIT search productivity enhancement
// @namespace mobiusevalon.tibbius.com
// @version 1.4
// @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/
// @include /^https{0,1}:\/\/\w{0,}\.?mturk\.com.+/
// @exclude /&hit_scraper$/
// @exclude /\/HM$/
// @grant none
// @downloadURL none
// ==/UserScript==
if(window.olympus === undefined) window.olympus = {};
olympus.athena = {
__name:"athena",
default_settings:{
interface_blocked_filter:true,
interface_highlighted_filter:true,
interface_qualified_filter:true,
interface_testable_filter:true,
interface_requestable_filter:true,
interface_unqualified_filter:false,
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_unqualified_filter:false,
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,
batch_mode:false,
bubble_hits:true,
frame_height:635,
not_accepted_clickthrough:true,
detail_expand:"unqualified"
},
__version:"1.2",
__configurable:function() {
return [
olympus.settings.explain({
type:"subheader",
desc:"Turk browsing settings"
}),
olympus.settings.generate({
option:"not_accepted_clickthrough",
type:"checkbox",
value:olympus.settings.get(olympus.athena,"not_accepted_clickthrough"),
name:"Clickthrough warning",
desc:
"Athena places a red box around the work frame of a HIT when you have not accepted it. This is convenient "+
"because you won't accidentally complete HITs or surveys without first having accepted them, which happens more "+
"often than you may think. This option will require you to click the warning box to interact with the HIT beneath "+
"it, while disabling this option will simply overlay a red screen on the HIT without needing to be clicked."
}),
olympus.settings.generate({
option:"detail_expand",
type:"dropdown",
selections:["none","qualified","testable","requestable","unqualified","all"],
value:olympus.settings.get(olympus.athena,"detail_expand"),
name:"Expand HIT details",
desc:
"The lowest feasibility level under which to expand the details of a HIT in the search list automatically. \"All\" "+
"and \"none\" should be obvious, and the rest of the options will expand details for any HIT with a feasibility "+
"at that level and above. For instance, selecting \"requestable\" means that all HITs that are requestable, "+
"testable, and qualified will have their details expanded, while unqualified and impossible HITs will not."
}),
olympus.settings.explain({
type:"subheader",
desc:"Scraper settings"
}),
olympus.settings.generate({
option:"bubble_hits",
type:"checkbox",
value:olympus.settings.get(olympus.athena,"bubble_hits"),
name:"Bubble new HITs",
desc:
"If enabled, new HITs will be displayed in a group at the top of the results table instead of being placed "+
"throughout by the sorting order."
})
];
},
__parse_settings:function(settings) {
olympus.settings.update(olympus.athena,settings);
olympus.athena.expand_hit_details();
},
__init:function() {
console.log("athena init");
olympus.help.add({
athena_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.
"+
"
Hovering over each of the items will tell you how many of that type of HIT are present on the page.
"+
"
"+
"
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 quickly and autonomously 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 impossible qualification tier.
"+
"
You possess the requisite qualifications, but their values are not sufficient to work on this HIT. These are most commonly 'quality control' quals where it is assigned some integer based on your overall performance. These are separated because you cannot work on the HIT you're looking at without completing some other HIT to change/raise your qualification score.
"+
"
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 TO, which is a value of 5 on the TO scale. These averages depend on your selections inside the Olympian Settings window, and are rounded to the nearest integer to determine its color. This means a 3.51 is color coded as 4 (good) while a 3.49 is color coded as 3 (fair).
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.
"+
"
Search termPretty obvious. When used, Turk will look for HITs that contain the term in requester names, HIT titles, HIT descriptions, and keywords.
"+
"
Search orderThis option will retrieve HITs from Turk in the specified order. It is not a sort order for the results. The / icon will alter the 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.
"+
"
Search intervalThe behavior of this option depends on the icon to next to the textbox.
When using a soft interval, Athena will not start the timer until after the result is done. For example, if this option is set to 5 seconds and it takes Athena 7 seconds to process the results, it will take 12 seconds between scrapes because the 5 second timer does not begin until after the 7 second scrape is completed.
When using a hard interval, Athena will forcibly stop what it is doing, display any results it has already processed, and begin a new scrape at the specified interval each time. Using the same 5 second timer and potential 7 second scrape defined earlier, Athena will quit what it is doing every 5 seconds to keep the given interval.
",
});
$("body").addClass("athena_interface").append(
$("")
.attr({
"data-pantheon":"athena",
"id":"athena"
})
.append(
$("").text(
"Congratulations, you know regular expressions well enough to find a beta feature that has not been "+
"announced. You can tinker here all you want, but don't expect anything to work."
),
$("").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() {
olympus.help.display("athena_search_options");
})
),
$("").append(
document.createTextNode("Search term:"),
$("").attr({
"id":"athena_min_to_pay",
"type":"text"
})
),
$("").append(
document.createTextNode("Search 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-chevron-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-chevron-down fa-chevron-up");
var dir = ($(this).hasClass("fa-chevron-up") ? "asc" : "desc");
$("#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("Search interval:"),
$("").attr({
"id":"athena_search_interval",
"type":"number",
"min":"0"
}),
$("")
.attr({
"class":"fa fa-lg fa-chain",
"title":"Using soft interval"
})
.click(function() {
$(this)
.toggleClass("fa-chain fa-chain-broken")
.attr("title",($(this).hasClass("fa-chain") ? "Using soft interval" : "Using hard interval"));
})
),
$(" "),
$("").append(
document.createTextNode("Batch size:"),
$("").attr({
"id":"athena_min_batch_size",
"type":"number",
"min":"0",
"step":"25"
})
),
$("").append(
document.createTextNode("TO pay:"),
$("").attr({
"id":"athena_min_to_pay",
"type":"number",
"min":"0",
"max":"5"
})
),
$("").append(
$("").attr({
"class":"fa fa-fw fa-lg fa-maxcdn",
"title":"Only Masters HITs"
})
),
$("").append(
document.createTextNode("Minimum feasibility:"),
$("")
.attr("id","athena_feasibility")
.append(
$("")
.attr("value","qualified")
.text("Qualified"),
$("")
.attr("value","testable")
.text("Testable"),
$("")
.attr("value","requestable")
.text("Requestable"),
$("")
.attr("value","unqualified")
.text("Unqualified"),
$("")
.attr("value","impossible")
.text("Impossible")
)
),
$("").append(
document.createTextNode("Minimum results:"),
$("").attr({
"id":"athena_min_results",
"type":"number",
"min":"1"
})
),
$("").append(
document.createTextNode("Minimum reward:"),
$("").attr({
"id":"athena_min_reward",
"type":"number",
"min":"0",
"step":"0.05"
})
)
),
$("").append(
$("")
.attr("class","control_box")
.append(
$("")
.attr("class","fa fa-fw fa-2x fa-play"),
olympus.settings.button("athena")
.addClass("fa-2x")
),
olympus.athena.create_filter_bar()
)
)
);
}
else {
// assistive functions on mturk pages
$("#sortresults_form").after(
olympus.athena.create_filter_bar()
);
// remove the stupid profile tasks box (since the x in the corner doesn't
// make it go away)
$("div.info-message-container").first().remove();
// check for the auto approval time. if this form element exists, then we are looking
// at/have accepted a HIT. we can display the aa time on the capsule and must also
// standardize the table layout for other code to work on it
var $aa = $("input[name='hitAutoAppDelayInSeconds']").first();
if($aa.length) {
var $title_container = $("td.capsulelink_bold"),
$requester_container = $("a[id^='requester.tooltip']").parent().next(),
hit_name = $title_container.text().collapseWhitespace(),
requester_name = $requester_container.text().collapseWhitespace(),
requester_id = ($("input[name='requesterId']").first().val() || olympus.utilities.href_requester_id($("a[href*='requesterId=']").first().text())),
reward = +$("span.reward",$("a[id^='reward.tooltip']").parent().next()).text().slice(1),
hits = +$("a[id^='number_of_hits.tooltip']").parent().next().text().collapseWhitespace(),
group_val = (reward*hits).toFixed(2),
$work_frame = $("iframe, #hit-wrapper").first(),
batch_mode = (olympus.settings.get(olympus.athena,"batch_mode") === true);
// standardize the display of this capsule with those that appear when viewing HITs
$title_container.empty().append(
$("")
.attr("class","capsulelink")
.text(hit_name)
);
$requester_container.empty().append(
$("")
.attr("class","requesterIdentity")
.text(requester_name)
);
if($.type(requester_id) === "string") $requester_container.children("span.requesterIdentity").wrap(
$("")
.attr("href",("https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+requester_id))
);
// add the AA time and group value to the capsule
$("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(olympus.utilities.dhms($aa.val()))
);
if(olympus.hermes) olympus.hermes.add_buttons();
$work_frame
.css("height",olympus.settings.get(olympus.athena,"frame_height"))
.before(
$("")
.attr("title","Batch mode keeps auto-accept checked and jumps to the top of the work area when the page loads, but only when you're working on a HIT and not simply previewing it.")
.append(
$("")
.attr({
"id":"skip_to_frame",
"type":"checkbox"
})
.prop("checked",batch_mode)
.click(function() {
olympus.settings.update(olympus.athena,"batch_mode",$(this).prop("checked"));
}),
document.createTextNode(" Batch mode")
),
$("")
.css("margin-left","20px")
.append(
document.createTextNode("Work area height: "),
$("")
.attr({
"id":"frame_height",
"type":"number"
})
.val(olympus.settings.get(olympus.athena,"frame_height"))
.on("blur onblur",function() {
olympus.settings.update(olympus.athena,"frame_height",$(this).val());
$("iframe, #hit-wrapper").first().css("height",$(this).val());
})
)
);
if($("form[name='hitForm']").first().attr("action") === "/mturk/accept") {
$work_frame
.wrap(
$("").css("position","relative")
)
.css("margin","0px")
.before(
$("")
.attr("id","hit_not_accepted")
.append(
$("").html(
"
You have not accepted this HIT.
"+
"This box is here to make sure that you don't accidentally complete a HIT without having accepted it first. "+
"This happens more often than you may think when you're in the middle of a batch or if you're booking through "+
"surveys and don't notice.
"+
"If the appearance of this box seems unexpected, then you may have been logged out of Turk or run afoul "+
"of a CAPTCHA.
"+
"Click this red box to interact with the HIT beneath."
)
)
.click(function() {
$(this).triggerHandler("disappear");
})
.on("disappear",function() {
$(this)
.css({
"background-color":"#f00",
"pointer-events":"none",
"opacity":"0.3"
})
.html("");
})
);
if(!olympus.settings.get(olympus.athena,"not_accepted_clickthrough")) $("#hit_not_accepted").triggerHandler("disappear");
var $captcha_input = $("input[name='userCaptchaResponse']");
if($captcha_input.length) $captcha_input.focus();
}
else if(batch_mode) {
// i have to register a window.load event to do these because amazon's scripts are doing so much junk beforehand
$(window).load(function() {
$("input[type='checkbox'][name='autoAcceptEnabled']").prop("checked",true);
$("iframe, #hit-wrapper").first().focus();
$(window).scrollTop($work_frame.offset().top);
});
}
}
// tag critical elements with class names for later, and insert interface elements while we're at it
$("a.capsulelink")
.before(
olympus.athena.list_management_element("include")
.on("context",function() {
return $(this).siblings("a.capsulelink[id]").first().text().compactAndLowerCase();
}),
olympus.athena.list_management_element("ignore")
.on("context",function() {
return $(this).siblings("a.capsulelink[id]").first().text().compactAndLowerCase();
})
)
.closest("table").parent().addClass("athena_hit_title")
.closest("table")
.addClass("athena_hit_table")
.find("span.requesterIdentity").closest("td").prepend(
olympus.athena.list_management_element("include")
.on("context",function() {
return $(this).siblings("a[href*='selectedSearchType=hitgroups']").first().text().compactAndLowerCase();
}),
olympus.athena.list_management_element("ignore")
.on("context",function() {
return $(this).siblings("a[href*='selectedSearchType=hitgroups']").first().text().compactAndLowerCase();
})
)
.end().end().closest("tr").addClass("athena_contains_hit")
.closest("table").addClass("athena_hit_list");
$("span.requesterIdentity").not("a > span.requesterIdentity").each(function() {
var requester_id = olympus.utilities.href_requester_id($("a[href*='requesterId=']",$(this).closest(".athena_hit_table")).first().attr("href"));
if($.type(requester_id) === "string") $(this).wrap(
$("")
.attr("href",("https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+requester_id))
);
});
olympus.athena.update_hs_visibility("ignore");
olympus.athena.update_hs_visibility("include");
olympus.athena.qualification_feasibility();
// amazon's internal script collapses all of the details at some point between document.ready
// and window.load, so i have to wait until after the latter to expand capsule details
// so that they don't get immediately collapsed again
$(window).load(function() {
olympus.athena.expand_hit_details();
});
var unique_rids = [];
$(".athena_hit_table").each(function() {
var rid = olympus.utilities.href_requester_id($("a[href*='requesterId=']",this).first().attr("href"));
if($.type(rid) === "string" && !unique_rids.contains(rid)) unique_rids.push(rid);
if(!window.location.href.contains("/mturk/myhits")) {
var $actions = $("span.capsulelink",this),
group_id = olympus.utilities.href_group_id($("a[href*='roupId=']",$actions).first().attr("href")), // not a typo
can_preview = !($actions.text().contains("Not Qualified to work on this HIT")),
$contact_space = $("td",$("div.capsuletarget",this)).last();
$actions.empty().append(
$('')
.text("View a HIT in this group")
.attr({
"target":"_blank",
"href":("/mturk/preview?groupId="+group_id),
"title":(can_preview ? "Preview this HIT without accepting it" : "The requester does not allow unqualified workers to preview this HIT")
})
.css("text-decoration",(can_preview ? "underline" : "line-through")),
document.createTextNode(" | "),
$('')
.text("Accept")
.attr({
"target":"_blank",
"href":("/mturk/previewandaccept?groupId="+group_id),
"title":(can_preview ? "Accept this HIT and begin working on it" : "You are not qualified to work on this HIT")
})
.css("text-decoration",(can_preview ? "underline" : "line-through"))
// document.createTextNode(" | "),
// $('')
// .text("Hoard")
// .click(function() {console.log("Hoard link doesn't do anything yet");})
// .css("text-decoration",(can_preview ? "underline" : "line-through"))
);
if(!$contact_space.children().length) $contact_space.empty().append(
$("")
.attr("href",(
"/mturk/contact?requesterId="+rid+
"&hitDescription="+encodeURIComponent($("a[id^='description.tooltip']",this).parent().next().text().collapseWhitespace())+
"&requesterName="+encodeURIComponent($("span.requesterIdentity",this).text().collapseWhitespace())+
"&subject=Regarding+Amazon+Mechanical+Turk+HIT+Type+"+group_id+
"&hitTitle="+$("a.capsulelink",this).text().collapseWhitespace()
))
.text("Contact the Requester of this HIT")
);
}
});
if(unique_rids.length) olympus.utilities.turkopticon(unique_rids,this._async_color_code,this);
}
},
_async_color_code:function(info) {
function to_confidence(avg,ttl) {
return ((ttl/(ttl + 25))*avg+(25/(ttl+25))*2.75);
}
function to_average(attrs,num_reviews) {
var sum = 0,
divisor = 0,
average = 0;
$.each(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") ? to_confidence(average,num_reviews) : average);
}
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";
}
}
var counts = {
great:0,
good:0,
fair:0,
poor:0,
awful:0,
no:0
};
if($.type(info) === "object") {
$(".athena_hit_table").each(function() {
var rid = olympus.utilities.href_requester_id($("a[href*='requesterId=']",this).first().attr("href")),
has_to = ($.type(info[rid]) === "object" && info[rid].hasOwnProperty("attrs")),
average = (has_to ? to_average(info[rid].attrs,info[rid].reviews) : 0),
rating = (has_to ? to_avg_to_text(Math.round(average)) : "no");
counts[rating]++;
$(".athena_hit_title",this)
.addClass(rating+"_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"+olympus.utilities.plural(info[rid].reviews)+", "+average.toFixed(2)+" avg ["+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("Athena: retrieved TO info is malformed");
$.each(counts,function(key,val) {
$("#athena_filter label.toggle_element[data-type='"+key+"_to']").triggerHandler("updateCount",val);
});
this.filter_hits();
},
create_filter_bar:function() {
function toggle_element(desc) {
var $label = $("")
.attr({
"class":"toggle_element",
"data-type":desc,
"data-name":(desc.slice(-3) === "_to" ? (desc.slice(0,desc.indexOf("_"))+" TO") : desc)
})
.on("updateCount",function(event,count) {
$(this).attr("title",(""+count+" "+$(this).attr("data-name")));
});
switch(desc) {
case "blocked": case "highlighted": case "qualified": case "testable": case "requestable": case "unqualified": case "impossible": {
$label
.attr("title",desc.ucFirst())
.append(
$("")
.attr({
"class":("fa fa-2x fa-fw "+olympus.athena.desc2fa(desc)+" "+(olympus.athena.setting_state(desc) ? "enabled" : "disabled")),
"data-filter":desc
})
.click(function() {
$(this).toggleClass("enabled disabled");
olympus.settings.update(olympus.athena,olympus.athena.setting_name($(this).attr("data-filter")),$(this).hasClass("enabled"));
olympus.athena.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",olympus.athena.setting_state(desc))
.click(function() {
olympus.settings.update(olympus.athena,olympus.athena.setting_name(desc),$(this).prop("checked"));
olympus.athena.filter_hits();
})
);
break;
}
}
return $label;
}
return $("")
.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("unqualified"),
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")
),
$("")
.attr({
"class":"fa fa-2x fa-fw fa-question-circle-o",
"title":"Filtering help"
})
.click(function() {
olympus.help.display("athena_filter_options");
})
);
},
desc2fa:function(d) {
// this simply became more convenient as i developed the script because
// it is loads easier to change the fa- class once here than try to
// search out every instance or find/replace
switch(d) {
case "blocked": case "ignore": return "fa-ban";
case "highlighted": case "include": return "fa-star";
case "qualified": return "fa-check";
case "testable": return "fa-pencil";
case "requestable": return "fa-lock";
case "unqualified": return "fa-times";
case "impossible": return "fa-warning";
}
},
expand_hit_details:function() {
var minimum = olympus.settings.get(olympus.athena,"detail_expand"),
$all_hits = $(".athena_hit_table"),
$expand;
if(minimum !== "none") {
var feasibilities = ["qualified","testable","requestable","unqualified"],
idx = feasibilities.indexOf(minimum),
classes = [];
for(var i=0;i<=idx;i++) classes.push(".ci_"+feasibilities[i]);
$expand = $(".athena_hit_table"+classes.join(", .athena_hit_table"));
}
$(".capsuletarget",$expand).show();
$(".capsuletarget",$all_hits.not($expand)).hide();
},
filter_hits:function() {
// steadily reduce the amount of visible hits, first by HitScraper blocklist, then by qualification selection, and then by TO average
$(".athena_contains_hit.hs_ignore").each(function() {
if($("#athena_filter .watchlists ."+olympus.athena.desc2fa("blocked")).first().hasClass("enabled")) $(this).addClass("enabled");
else $(this).removeClass("enabled");
});
$("#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
$(".athena_hit_table.hs_include").each(function() {
if($("#athena_filter .watchlists ."+olympus.athena.desc2fa("highlighted")).first().hasClass("enabled")) $(this).addClass("enabled");
else $(this).removeClass("enabled");
});
},
list_management_element:function(type) {
return $("")
.attr({
"class":("fa fa-lg fa-fw athena_"+type+"_button fa-"+(type === "ignore" ? "times" : "asterisk")),
"data-type":type
})
.click(function() {
$(this).triggerHandler("manage_list",$(this).triggerHandler("context"));
olympus.athena.filter_hits();
})
.on("manage_list",function(evt,string) {
function hslist2array() {
var array = localStorage[ls_key];
if($.type(array) === "string" && array.length) return array.split("^");
return [];
}
var ls_key = ("scraper_"+$(this).attr("data-type")+"_list"),
hs_list = hslist2array(),
idx = hs_list.indexOf(string);
if($(this).hasClass("enabled")) {
if(idx > -1) hs_list.splice(idx,1);
}
else {
if(idx === -1) hs_list.push(string);
}
localStorage[ls_key] = (hs_list.join("^"));
olympus.athena.update_hs_visibility($(this).attr("data-type"));
})
.on("disable",function() {
var block = ($(this).attr("data-type") === "ignore");
$(this)
.removeClass("enabled")
.addClass("disabled")
.attr("title",(block ? "Block" : "Highlight"));
})
.on("enable",function() {
var block = ($(this).attr("data-type") === "ignore");
$(this)
.removeClass("disabled")
.addClass("enabled")
.attr("title",(block ? "Unblock" : "Unhighlight"));
});
},
qualification_feasibility:function() {
function impossible(qual) {
var LT = "lt",
GT = "gt";
function comparator() {
var d = qual.contains("not"),
e = +qual.match(/(\d{1,3})$/i)[1];
if(qual.contains("greater than")) {
if(d) return [LT,e+1];
else return [GT,e];
}
if(qual.contains("less than")) {
if(d) return [GT,e-1];
else return [LT,e];
}
}
if(/exc: \[[\d-]+\]/i.test(qual)) return true;
if(/inc: \[[\d-]+\]/i.test(qual)) return true;
if(/masters has been granted$/i.test(qual)) return true;
if(/^ibotta/i.test(qual)) return true;
if(/^location is/i.test(qual)) return true;
if(/^hit approval rate/i.test(qual)) {
var o = comparator();
if(o[0] === GT && o[1] === 100) return true;
if(o[0] === LT) return true;
}
if(/^total approved hits/i.test(qual)) {
var p = comparator();
if(p[0] === LT) return true;
}
return false;
}
function precedence(old,cur) {
function q2n(q) {
if(q === "impossible") return 5;
else if(q === "unqualified") return 4;
else if(q === "requestable") return 3;
else if(q === "testable") return 2;
else if(q === "qualified") return 1;
else return 0;
}
if(q2n(cur) > q2n(old)) return cur;
else return old;
}
var counts = {
impossible:0,
unqualified:0,
requestable:0,
testable:0,
qualified:0
};
$("a[id^='qualificationsRequired.tooltip']").closest("table").each(function() {
var hit_feasibility = "";
if($(this).find("td:nth-child(2)").text().trim() === "None") hit_feasibility = "qualified";
else $("tr:not(:first-of-type) td:nth-child(3)",$(this)).each(function() {
function lnk(s) {
return (s === "requestable" || s === "testable");
}
var qual_tier,
qual_desc = $(this).prev().prev().text().collapseWhitespace(),
qual_tooltip = $(this).text().collapseWhitespace(),
meet_qual = $(this).text().contains("You meet this qualification");
if(!meet_qual) {
if(impossible(qual_desc)) qual_tier = "impossible";
else if(qual_tooltip.contains("Request Qualification")) qual_tier = "requestable";
else if(qual_tooltip.contains("Qualification test")) qual_tier = "testable";
else qual_tier = "unqualified";
}
else qual_tier = "qualified";
hit_feasibility = precedence(hit_feasibility,qual_tier);
var $elem = $(lnk(qual_tier) ? "" : "").attr("class",olympus.athena.desc2fa(qual_tier));
if(lnk(qual_tier)) $elem.attr({"href":$(this).children("a").first().attr("href"),"target":"_blank"});
$(this).empty().append(
$elem
.addClass("ci_qual fa fa-fw fa-lg")
.attr("title",(qual_tier === "impossible" ? "This qualification is impossible" : qual_tooltip))
);
});
//$("td",$("tr",this).first()).last().text("test");
$(this).closest(".athena_hit_table").addClass("ci_"+hit_feasibility);
counts[hit_feasibility]++;
});
$.each(counts,function(key,val) {
$("#athena_filter label.toggle_element[data-type='"+key+"']").triggerHandler("updateCount",val);
});
},
setting_name:function(desc) {
return (($("body").hasClass("athena_interface") ? "interface" : "assist")+"_"+desc+"_filter");
},
setting_state:function(desc) {
return olympus.settings.get(this,this.setting_name(desc));
},
update_hs_visibility:function(name) {
// this function tags hits with blocked or highlighted status but does not yet
// actually hide them. this function is only called on page load and when the
// blocklist or includelist are changed
function hs_list() {
var ls_string = localStorage["scraper_"+name+"_list"],
hs_array = (($.type(ls_string) === "string" && ls_string.trim().length) ? ls_string.split("^") : []),
result = {
list:name,
plaintext:[],
regex:[],
get len() {
return (this.plaintext.length || this.regex.length);
},
match:function(string) {
if(this.len && (this.plaintext.contains(string) || this.regex.regex_match(string))) return true;
return false;
},
eval_hit:function($hit) {
var listed_hit = false;
function class_target() {
return ((result.list === "ignore") ? $($hit).closest(".athena_contains_hit") : $($hit));
}
if(this.len) { // no reason to do anything if there is nothing blocked/highlighted
var listed_requester = this.match($("span.requesterIdentity",$hit).text().compactAndLowerCase()),
listed_title = this.match($("a.capsulelink",$hit).text().compactAndLowerCase());
listed_hit = (listed_requester || listed_title);
if(listed_hit) class_target().addClass("hs_"+this.list);
else class_target().removeClass("hs_"+this.list);
$("a.capsulelink",$hit).siblings("span.athena_"+this.list+"_button").first().triggerHandler(listed_title ? "enable" : "disable");
$("span.requesterIdentity",$hit).closest("td").find("span.athena_"+this.list+"_button").first().triggerHandler(listed_requester ? "enable" : "disable");
}
return listed_hit;
}
};
if(hs_array.length) {
hs_array.filter(function(val) {
val = val.compactAndLowerCase();
if(val.contains("*")) result.regex.push(new RegExp(val.replace(/[-[\]{}()+?.,\\^$|#\s]/g,"\\$&").replace(/\*+/g,".+"),"gi"));
else result.plaintext.push(val);
return false;
});
}
return result;
}
var hs_obj = hs_list(),
count = 0;
if(hs_obj.len) {
$(".athena_hit_table").each(function() {
count += (hs_obj.eval_hit(this) ? 1 : 0);
});
}
$("#athena_filter label.toggle_element[data-type='"+(name === "ignore" ? "blocked" : "highlighted")+"']").triggerHandler("updateCount",count);
}
};