// ==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 = $("