// ==UserScript== // @name Athena HIT search productivity enhancement // @namespace mobiusevalon.tibbius.com // @version 1.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/ // @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", __settings:{}, __init:function() { console.log("athena init"); this.__settings = olympus.settings.init(this); olympus.help.add({ athena_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.

", 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 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.
" }); olympus.style.add( ".ci_qual.[qualified], #athena_filter .qualifications .[qualified].enabled, #olympian_help .[qualified] {color: #050;}"+ ".ci_qual.[unqualified], .ci_qual.[impossible], #athena_filter .watchlists .[blocked].enabled, #athena_filter .qualifications .[unqualified].enabled, #athena_filter .qualifications .[impossible].enabled, #olympian_help .[blocked], #olympian_help .[unqualified], #olympian_help .[impossible] {color: #a45555;}"+ "#athena_filter .watchlists .[highlighted].enabled, #olympian_help .[highlighted] {color: #ffd37b;}"+ ".ci_qual.[requestable], #athena_filter .qualifications .[requestable].enabled, #olympian_help .[requestable] {color: #eebb56; text-decoration: none;}"+ ".ci_qual.[testable], #athena_filter .qualifications .[testable].enabled, #olympian_help .[testable] {color: #005; text-decoration: none;}"+ ".ci_qual {width: 16px; height: 16px; font-size: 150%; text-align: center;}"+ ".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, #olympian_help .toggle_element {display: inline-block; width: 24px; height: 24px; text-align: center;}"+ "#athena_filter .to_ratings .no_to_rating, #olympian_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_ignore.enabled {display: none;}"+ ".hs_include.enabled {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;}"+ ".block_item, .athena_ignore_button, .athena_include_button {cursor: pointer;}"+ ".athena_ignore_button.disabled, .athena_include_button.disabled {opacity: 0.25;}"+ ".athena_contains_hit.filtered_out {display: none;}"+ "#olympian_help table tr td:nth-child(1) {vertical-align: top; text-align: center;}"+ "#olympian_help table tr td:nth-child(2) {padding-bottom: 15px;}", { qualified:olympus.athena.desc2fa("qualified"), testable:olympus.athena.desc2fa("testable"), requestable:olympus.athena.desc2fa("requestable"), unqualified:olympus.athena.desc2fa("unqualified"), impossible:olympus.athena.desc2fa("impossible"), blocked:olympus.athena.desc2fa("blocked"), highlighted:olympus.athena.desc2fa("highlighted") } ); Array.prototype.regex_match = function(string) { return (this.filter(function(regexp) { if($.type(regexp) === "regexp" && regexp.test(string)) return true; }).length > 0); }; String.prototype.compactAndLowerCase = function() { return this.toLowerCase().collapseWhitespace().trim(); }; // 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"); if(/(?:&|\?)athena$/i.test(window.location.href)) { // the athena scraper interface proper $("head").html("").append( $("").text("Athena HIT scraper") ); $("body").html(""); $("body").removeAttr("onload").addClass("athena_interface").append( $("<div/>") .attr("id","athena") .append( $("<h1/>").text("Athena HIT scraper"), $("<fieldset/>").attr("id","athena_search").append( $("<legend/>").append( $("<span/>") .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"), $("<span/>") .attr("class","fa fa-fw fa-lg fa-question-circle-o") .click(function() { olympus.help.display("athena_search_options"); }) ), $("<label/>").append( document.createTextNode("Keyword:"), $("<input/>").attr({ "id":"athena_min_to_pay", "type":"text" }) ), $("<label/>").append( document.createTextNode("Order:"), $("<select/>") .attr("id","athena_search_order") .append( $("<option/>") .attr("value","date") .text("Age (newest)"), $("<option/>") .attr("value","reward") .text("Reward (most)"), $("<option/>") .attr("value","batch") .text("Batch size (most)"), $("<option/>") .attr("value","date") .text("Title (A-Z)") ), $("<span/>") .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)+")"); }); }) ), $("<label/>").append( document.createTextNode("Reward:"), $("<input/>").attr({ "id":"athena_min_reward", "type":"number", "min":"0", "step":"0.05" }) ), $("<label/>").append( document.createTextNode("Batch size:"), $("<input/>").attr({ "id":"athena_min_batch_size", "type":"number", "min":"1", "step":"25" }) ), $("<label/>").append( document.createTextNode("TO pay:"), $("<input/>").attr({ "id":"athena_min_to_pay", "type":"number", "min":"0", "max":"5" }) ), $("<label/>").append( document.createTextNode("Skip unqualified:"), $("<input/>").attr({ "id":"athena_skip_unqualified", "type":"checkbox" }) ), $("<label/>").append( $("<span/>").attr({ "class":"fa fa-fw fa-lg fa-maxcdn", "title":"Only Masters HITs" }) ) ), $("<fieldset/>").append( $("<legend/>").append( $("<span/>") .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"), $("<span/>") .attr("class","fa fa-fw fa-lg fa-question-circle-o") .click(function() { olympus.help.display("athena_filter_options"); }) ), olympus.ahtnea.create_filter_bar() ) ) ); } else { // assistive functions on mturk pages $("form#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.requester_id_from_href($("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, div#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( $("<a/>") .attr("class","capsulelink") .text(hit_name) ); $requester_container.empty().append( $("<a/>") .attr("href",(($.type(requester_id) === "string") ? ("https://www.mturk.com/mturk/searchbar?selectedSearchType=hitgroups&requesterId="+requester_id) : "#")) .append( $("<span/>") .attr("class","requesterIdentity") .text(requester_name) ) ); // 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( $("<td/>") .attr({ "align":"right", "valign":"top", "class":"capsule_field_title", "nowrap":"" }) .text("Group value:\xa0\xa0"), $("<td/>") .attr({ "align":"left", "valign":"top", "class":"capsule_field_text", "nowrap":"" }) .text("$"+group_val), $("<td/>").append( $("<img/>") .attr("src","/media/spacer.gif") .css({ "width":"25px", "height":"1px", "border":"0px" }) ), $("<td/>") .attr({ "align":"right", "valign":"top", "class":"capsule_field_title", "nowrap":"" }) .text("AA:\xa0\xa0"), $("<td/>") .attr({ "align":"left", "valign":"top", "class":"capsule_field_text", "nowrap":"" }) .text(olympus.athena.dhms($aa.val())) ); if(olympus.hermes) olympus.hermes.add_buttons(); $work_frame.before( $("<input/>") .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(" Jump to work frame on page load (batch mode)") ); if(batch_mode) $(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"); olympus.athena.update_hs_visibility("ignore"); olympus.athena.update_hs_visibility("include"); olympus.athena.qualification_feasibility(); $(".athena_hit_table:not(.ci_impossible) div.capsuletarget").show(); var unique_rids = []; $(".athena_hit_table").each(function() { var rid = olympus.utilities.requester_id_from_href($("a[href*='requesterId']",this).first().attr("href")); if($.type(rid) === "string" && unique_rids.indexOf(rid) === -1) unique_rids.push(rid); }); if(unique_rids.length) olympus.utilities.turkopticon(unique_rids,this._async_color_code,this); // 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 = olympus.utilities.group_id_from_href($preview.attr("href")), cannot_preview = ($preview.attr("href").indexOf("hitGroupId") > -1); $(this).empty().append( $('<a/>') .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(" | "), $('<a/>') .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(" | "), $('<a/>') .text("Hoard") .click(function() {console.log("Hoard link doesn't do anything yet");}) .css("text-decoration",(cannot_preview ? "line-through" : "underline")) ); } }); } } }, _async_color_code:function(info) { function to_average(attrs) { // decimals are valid, as is zero (which removes that dimension from the average) // TODO: make these configurable 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"; } } 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.requester_id_from_href($("a[href*='requesterId']",this).first().attr("href")), has_to = ($.type(info[rid]) === "object" && info[rid].hasOwnProperty("attrs")), rating = (has_to ? to_avg_to_text(Math.round(to_average(info[rid].attrs))) : "no"); counts[rating]++; $(".athena_hit_title",this) .addClass(rating+"_to_rating") .find("tr").first().after( $("<tr/>").append( $("<td/>") .attr("colspan","3") .append( $("<a/>") .attr({ "href":("https://turkopticon.ucsd.edu/"+rid), "target":"_blank" }) .append( document.createTextNode(has_to ? (info[rid].reviews+" review"+olympus.utilities.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"), $("<b/>") .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 = $("<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( $("<span/>") .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( $("<input/>") .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; } var $filter_bar = $("<div/>") .attr("id","athena_filter") .append( $("<div/>") .attr("class","watchlists") .append( toggle_element("blocked"), toggle_element("highlighted") ), $("<div/>") .attr("class","qualifications") .append( toggle_element("qualified"), toggle_element("testable"), toggle_element("requestable"), toggle_element("unqualified"), toggle_element("impossible") ), $("<div/>") .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( $("<span/>") .attr({ "class":"fa fa-2x fa-fw fa-question-circle-o", "title":"Filtering help" }) .click(function() { olympus.help.display("athena_filter_options"); }) ); return $filter_bar; }, default_settings:function() { return { 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 }; }, 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"; } }, 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 = (secs/multiple); return (""+zeroes(units)+" "+name+olympus.utilities.plural(units)); } if($.type(secs) !== "number") secs = Math.round(secs*1); if(secs >= 86400) return output(86400,"day"); else if(secs >= 3600) return output(3600,"hour"); else if(secs >= 60) return output(60,"minute"); else return output(1,"second"); }, 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 $("<span/>") .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.indexOf("not") > -1), e = +qual.match(/(\d{1,3})$/i)[1]; if(qual.indexOf("greater than") > -1) { if(d) return [LT,e+1]; else return [GT,e]; } if(qual.indexOf("less than") > -1) { 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().indexOf("You meet this qualification") > -1); if(!meet_qual) { if(impossible(qual_desc)) qual_tier = "impossible"; else if(qual_tooltip.indexOf("Request Qualification") > -1) qual_tier = "requestable"; else if(qual_tooltip.indexOf("Qualification test") > -1) qual_tier = "testable"; else qual_tier = "unqualified"; } else qual_tier = "qualified"; hit_feasibility = precedence(hit_feasibility,qual_tier); var $elem = $(lnk(qual_tier) ? "<a/>" : "<span/>").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)) ); }); $(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.indexOf(string) > -1 || 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.indexOf("*") > -1) 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); } };